Leetcode算法笔记-(链表篇)

本文是LeetCode算法入门刷题笔记,好记性不如烂笔头,多背然后自己多默写几遍自然就理解了。若有错误的地方也请各位大佬指正哈

首先让我们回顾一下链表定义:

链表(linked list)是一种线性数据结构,其中的每个元素都是一个节点对象,各个节点通过“引用”相连接,有的叫指针。

链表的组成单位是节点(node)对象。每个节点都包含两项数据:节点的“值”和指向下一节点的“引用”。

  • 链表的首个节点被称为“头节点”,最后一个节点被称为“尾节点”。
  • 尾节点指向的是“空”,它在Java、C++ 和Python 中分别被记为null、nullptr 和None 。
  • 在C、C++、Go 和Rust 等支持指针的语言中,上述“引用”应被替换为“指针”。
  • 链表定义与存储方式
  • 1. 反转链表

  • 方法一:迭代(双指针)

    思路:遍历链表,并在访问各节点时修改next指针指向

  • class Solution:
        def reverse_list(self, head):
            cur, pre = head, None
            while cur:
                temp = cur.next
                cur.next = pre
                pre = cur
                cur = temp
            return pre

    时间复杂度:O(n)

    空间复杂度:O(1),变量pre和cur使用常数大小额外空间

    方法二:递归

    思路:当越过尾节点后终止递归,在回溯时修改各节点的next指针指向

  • class Solution:
        def reverseList(self, head):
            def recur(cur, pre):
                if not cur: return pre
                res = recur(cur.next, cur)
                cur.next = pre
                return res
            return recur(head, None)
    

    时间复杂度:O(n)

    空间复杂度:O(n),遍历链表的递归深度达到n,系统使用O(n)大小额外空间。

    小结:考虑什么时候是循环终止条件,当然是链表为空的时候,所以方法一(迭代)循环开始是while cur:,方法二(递归),当越过尾节点后终止递归,if not cur: return pre。

    为了方便查看,打印链表。

  • class ListNode:
        def __init__(self, val):
            self.val = val
            self.next = None
    
    def printlist(head):
        cur = head
        while cur:
            print(cur.val, end='->' if cur.next else " ")
            cur = cur.next
        print()
    
    #双指针,迭代
    class Solution:
        def reverselist(self, head):
            cur, pre = head, None
            while cur:
                temp = cur.next
                cur.next = pre
                pre = cur
                cur = temp
            return pre
    #递归
    class Solution2:
        def reverselist(self, head):
            def recur(cur, pre):
                if not cur: return pre
                res = recur(cur.next, cur)
                cur.next = pre
                return res
            return recur(head, None)
    
    n1, n2, n3, n4, n5 = ListNode(1), ListNode(2), ListNode(3), ListNode(4), ListNode(5)
    n1.next, n2.next, n3.next, n4.next = n2, n3, n4, n5
    print("原始链表:")
    printlist(n1)
    
    result = Solution2()
    out_reverselist = result.reverselist(n1)
    print("反转链表:")
    printlist(out_reverselist)
    

    2. 奇偶链表

    这道题我刚开始做的时候,老是会丢掉一个循环终止条件,画图加深印象!!!

    思路:将奇数节点和偶数节点分开,然后将奇数尾节点指向偶数头节点,可以分成两种情况,链表长度为奇数和偶数(循环终止条件不同)。

    情况1:链表长度为奇数

  • 循环终止条件:even为none

    情况2:链表长度为偶数

    循环终止条件:even.next为none

  • class Solution:
        def oddevenlist(self, head):
            if not head: return head
            evenhead = head.next
            odd, even = head, evenhead
            while even and even.next:
                odd.next = even.next
                odd = odd.next
                even.next = odd.next
                even = even.next
            odd.next = evenhead
            return head
    

    时间复杂度:O(n),其中n是链表的节点数。需要遍历链表中的每个节点,并更新指针。

    空间复杂度:O(1),只需要维护有限的指针

    为方便查看,打印链表

  • class ListNode:
        def __init__(self, val):
            self.val = val
            self.next = None
    def printlist(head):
        cur = head
        while cur:
            print(cur.val, end='->' if cur.next else " ")
            cur = cur.next
        print() #换行
    
    class Solution:
        def oddevenlist(self, head):
            if not head: return head
            evenhead = head.next
            odd, even = head, evenhead
            while even and even.next:
                odd.next = even.next
                odd = odd.next
                even.next = odd.next
                even = even.next
            odd.next = evenhead
            return head
    n1, n2, n3, n4, n5 = ListNode(1), ListNode(2), ListNode(3), ListNode(4), ListNode(5)
    n1.next, n2.next, n3.next, n4.next = n2, n3, n4, n5
    print("原始链表:")
    printlist(n1)
    
    result = Solution()
    out_reverselist = result.oddevenlist(n1)
    print("转换后链表:")
    printlist(out_reverselist)
    

    3. 合并两个有序链表

 思路:由于两个链表是有序的,用双指针list1,list2遍历两个链表,比较list1.val跟list2.val大小确定添加哪个节点,想想终止条件,只要某个链表节点为none,跳出循环,记住不要忘记cur.next = list1 if list1 else list2,确保不落下最后一个节点。图引用的是Krahets大佬的。

 

class Solution:
    def mergelist(self, list1, list2):
        cur = dummy = ListNode(0)
        while list1 and list2:
            if list1.val < list2.val:
                cur.next = list1
                list1 = list1.next
            else:
                cur.next = list2
                list2 = list2.next
            cur = cur.next
        cur.next = list1 if list1 else list2
        return dummy.next

时间复杂度O(m+n):m、n分别为list1、list2的长度,合并操作需遍历两链表。

空间复杂度O(1):节点引用dummy,cur使用常数大小的额外空间。

为方便查看,打印链表

class Solution:
    def mergelist(self, list1, list2):
        cur = dummy = ListNode(0)
        while list1 and list2:
            if list1.val < list2.val:
                cur.next = list1
                list1 = list1.next
            else:
                cur.next = list2
                list2 = list2.next
            cur = cur.next
        cur.next = list1 if list1 else list2
        return dummy.next

class ListNode:
    def __init__(self, val):
        self.val = val
        self.next = None
def printlist(head):
    cur = head
    while cur:
        print(cur.val, end='->' if cur.next else " ")
        cur = cur.next
    print() #换行
n1, n2, n3, n4, n5 = ListNode(1), ListNode(2), ListNode(3), ListNode(4), ListNode(5)
n1.next, n2.next, n3.next, n4.next = n2, n3, n4, n5
print("原始链表1:")
printlist(n1)

p1, p2, p3 = ListNode(2), ListNode(4), ListNode(7)
p1.next, p2.next = p2, p3
print("原始链表2:")
printlist(p1)

result = Solution()
out_mergelist = result.mergelist(n1, p1)
print("合并后链表:")
printlist(out_mergelist)

4. 合并K个有序链表

思路:用最小堆实现。初始把所有链表的头节点入堆,然后不断弹出堆中最小节点x,如果x.next不为空就加入堆中。循环直到堆为空。把弹出的节点按顺序拼接起来,就得到了答案。参考灵神大佬

ListNode.__lt__ = lambda a, b: a.val < b.val
class Solution:
    def mergeKLists(self, lists):
        cur = dummy = ListNode()
        h = [head for head in lists if head]
        heapify(h)
        while h:
            code = heappop(h)
            if code.next:
                heappush(h, code.next)
            cur.next = code
            cur = cur.next
        return dummy.next

Note:在Pycharm中要加上这一句from heapq import heappop, heapify, heappush
时间复杂度:O(nlogk),其中k为lists的长度,n为所有链表的节点数之和。

空间复杂度:O(k)。堆中至多有k个元素(每个链表的头节点)

为方便查看,打印链表

from heapq import heappush, heappop, heapify
class ListNode:
    def __init__(self, val):
        self.val = val
        self.next = None
ListNode.__lt__ = lambda a, b: a.val < b.val

class Solution:
    def mergeKlist(self, lists):
        cur = dummy = ListNode(0)
        h = [head for head in lists if head]
        heapify(h)
        while h:
            node = heappop(h)
            if node.next:
                heappush(h, node.next)
            cur.next = node
            cur = cur.next
        return dummy.next

def printlist(head):
    cur = head
    while cur:
        print(cur.val, end='->' if cur.next else " ")
        cur = cur.next
    print() #换行

n1, n2, n3, n4, n5 = ListNode(1), ListNode(2), ListNode(3), ListNode(4), ListNode(5)
n1.next, n2.next, n3.next, n4.next = n2, n3, n4, n5
print("原始链表1:")
printlist(n1)

p1, p2, p3 = ListNode(2), ListNode(4), ListNode(7)
p1.next, p2.next = p2, p3
print("原始链表2:")
printlist(p1)

q1, q2, q3 = ListNode(3), ListNode(8), ListNode(9)
q1.next, q2.next = q2, q3
print("原始链表3:")
printlist(q1)

result = Solution()
out_mergelist = result.mergeKlist([n1, p1, q1])
print("合并后链表:")
printlist(out_mergelist)

5. 排序链表

思路:采用归并排序解题,将链表从中间分成两部分,递归地对左半部分和右半部分进行排序,然后合并两个有序链表。

链表拆分:使用快慢指针找到链表的中间节点,将链表从中间断开,分为左半部分和右半部分。

链表合并:合并两个有序链表,返回合并后的链表头节点

class Solution:
    def mergelist(self, head):
# 终止条件:链表为空或只有一个节点
        if not head or not head.next:
            return head
# 分割链表
        slow = fast = head
        pre = None
#考虑链表长度为奇数或偶数情况,与第2题奇偶链表类似
        while fast and fast.next:
            pre = slow
            slow = slow.next
            fast = fast.next.next
        pre.next = None #断开链表
        mid = slow #右半部分头节点
#递归排序左半部分和右半部分
        left = self.mergelist(head)
        right = self.mergelist(mid)
        return self.merge(left, right)
    def merge(self, list1, list2):
        cur = dummy = ListNode(0)
        while list1 and list2:
            if list1.val < list2.val:
                cur.next = list1
                list1 = list1.next
            else:
                cur.next = list2
                list2 = list2.next
            cur = cur.next
        cur.next = list1 if list1 else list2
        return dummy.next

时间复杂度:O(nlogn),其中n是链表长度。递归式T(n)=2T(n/2)+O(n),由主定理可得时间复杂度为O(nlogn)。证明链接在这里。

空间复杂度:O(logn)。递归需要O(logn)的栈开销

为方便查看,打印链表

class Solution:
    def mergelist(self, head):
        if not head or not head.next:
            return head
        slow = fast = head
        pre = None
        while fast and fast.next:
            pre = slow
            slow = slow.next
            fast = fast.next.next
        pre.next = None
        mid = slow

        left = self.mergelist(head)
        right = self.mergelist(mid)
        return self.merge(left, right)
    def merge(self, list1, list2):
        cur = dummy = ListNode(0)
        while list1 and list2:
            if list1.val < list2.val:
                cur.next = list1
                list1 = list1.next
            else:
                cur.next = list2
                list2 = list2.next
            cur = cur.next
        cur.next = list1 if list1 else list2
        return dummy.next

class ListNode:
    def __init__(self, val):
        self.val = val
        self.next = None

def printlist(head):
    cur = head
    while cur:
        print(cur.val, end='->' if cur.next else " ")
        cur = cur.next
    print() #换行
n1, n2, n3, n4 = ListNode(1), ListNode(5), ListNode(3), ListNode(2)
n1.next, n2.next, n3.next = n2, n3, n4
print("原始链表:")
printlist(n1)

result = Solution()
out_mergelist = result.mergelist(n1)
print("排序后链表:")
printlist(out_mergelist)

6. 删除链表的节点

思路:遍历链表,找到所有值为val的节点并删除,图是Krahets大佬的。

class Solution:
    def deletelist(self, head, val):

        if not head: return head #确保链表非空
#LeetCode官网直接用cur = dummy = ListNode()就可以
#这里Pycharm会报错,要定义下
        cur = dummy = ListNode(0, next=head)
        if head.val == val: return head.next

        while cur.next:
            if cur.next.val == val:
                cur.next = cur.next.next
            else:
                cur = cur.next
        return dummy.next

时间复杂度O(n):n为链表长度,删除操作平均需循环n/2次,最差n次。

空间复杂度O(1):cur, dummy占用常数大小额外空间

为方便查看,打印链表

class ListNode:
    def __init__(self, val, next=None):
        self.val = val
        self.next = next

class Solution:
    def deletelist(self, head, val):

        if not head: return head

        cur = dummy = ListNode(0, next=head)
        if head.val == val: return head.next

        while cur.next:
            if cur.next.val == val:
                cur.next = cur.next.next
            else:
                cur = cur.next
        return dummy.next

def printlist(head):
    cur = head
    while cur:
        print(cur.val, end='->' if cur.next else " ")
        cur = cur.next
    print() #换行
n1, n2, n3, n4 = ListNode(1), ListNode(5), ListNode(3), ListNode(2)
n1.next, n2.next, n3.next = n2, n3, n4
print("原始链表:")
printlist(n1)

result = Solution()
out_mergelist = result.deletelist(n1, 3)
print("删除后链表:")
printlist(out_mergelist)

7. 删除链表的倒数第N个节点

思路:采用双指针遍历链表,右指针比左指针提前走n步,当右指针走到头,左指针正好是倒数第N个节点的前一个节点,这里一定要自己动手画一下两个指针如何前进的。

class Solution:
    def deleteKlist(self, head, n):
        if not head: return head #确保链表非空
        left = right = dummy = ListNode(0, next=head)
#右指针先走n步
        for _ in range(n):
            right = right.next
        while right.next:
            left = left.next
            right = right.next
        left.next = left.next.next  #左指针的下一个节点就是倒数第N个节点
        return dummy.next

时间复杂度:O(n, n 为链表的长度。

空间复杂度:O(1),仅用到若干额外变量

为方便查看,打印链表

class ListNode:
    def __init__(self, val, next=None):
        self.val = val
        self.next = next
def printlist(head):
    cur = head
    while cur:
        print(cur.val, end='->' if cur.next else " ")
        cur = cur.next
    print() #换行

class Solution:
    def deleteKlist(self, head, n):
        if not head: return head
        left = right = dummy = ListNode(0, next=head)
        for _ in range(n):
            right = right.next
        while right.next:
            left = left.next
            right = right.next
        left.next = left.next.next
        return dummy.next
n1, n2, n3, n4 = ListNode(1), ListNode(5), ListNode(3), ListNode(2)
n1.next, n2.next, n3.next = n2, n3, n4
print("原始链表:")
printlist(n1)

result = Solution()
out_mergelist = result.deleteKlist(n1, 2)
print("删除后链表:")
printlist(out_mergelist)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值