手把手刷链表题目

1. 一文搞懂单链表的六大解题套路

21.合并两个有序链表(简单)

在这里插入图片描述
简单题,双指针就可以,还有一个传值还是传引用的问题可以看这篇:python函数传参是传值还是传引用?

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
        dummy = ListNode()
        p = dummy
        while list1 != None and list2 != None:
            if list1.val < list2.val:
                p.next = list1
                list1 = list1.next
                p = p.next
            elif list2.val <= list1.val:
                p.next = list2
                list2 = list2.next
                p = p.next
        if list1 != None:
            p.next = list1
        if list2 != None:
            p.next = list2
        return dummy.next 

23.合并K个升序链表(困难)

在这里插入图片描述
与上面那道题一样的思路,只不过上面那道题只需要比较两个指针的值,这道题有k个,要是挨个比较要o(k)的复杂度。但是我们可以用priority queue这个数据结构把时间复杂度降到o(logk)
python 里面priority queue怎么用,看这篇文章: 用Python实现优先级队列

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def mergeKLists(self, lists: List[ListNode]) -> ListNode:
        def __lt__(self, other):
            return self.val < other.val
        ListNode.__lt__ = __lt__
        dummy = ListNode()
        p = dummy
        heap = []
        for ln in lists:
            if ln:
                heapq.heappush(heap, ln)
        while heap:
            smallest = heapq.heappop(heap)
            p.next = smallest
            p = p.next
            
            if smallest.next:
                heapq.heappush(heap, smallest.next)
        return dummy.next

注意的点有:

  1. 要修改ListNode.lt 方法以便使用heapq
  2. 原题中某些ListNode可能是NoneType,所以heappush的时候要加以判断, 保证加入的ListNode不是None
  3. 在while循环中, smallest的下一个元素在被push到heap里之前也要判断, 不能加入None

141.环形链表(简单)

142.环形链表 II(中等)

19.删除链表的倒数第 N 个结点(中等)

876.链表的中间结点(简单)

以上这些题都可以用快慢指针来做

160.相交链表(简单)

在这里插入图片描述
如果用两个指针 p1 和 p2 分别在两条链表上前进,并不能同时走到公共节点,也就无法得到相交节点 c1。

解决这个问题的关键是,通过某些方式,让 p1 和 p2 能够同时到达相交节点 c1。

所以,我们可以让 p1 遍历完链表 A 之后开始遍历链表 B,让 p2 遍历完链表 B 之后开始遍历链表 A,这样相当于「逻辑上」两条链表接在了一起。

如果这样进行拼接,就可以让 p1 和 p2 同时进入公共部分,也就是同时到达相交节点 c1

需要注意的是,不相交的情况也可以看作相交于None这个节点,也就是下图c1是None的情况
在这里插入图片描述

class Solution:
    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
        pa = headA
        pb = headB
        while pa != pb:
            if pa:
                pa = pa.next
            else:
                pa = headB
            if pb:
                pb = pb.next
            else:
                pb = headA
        return pa

2.递归反转链表的一部分递归反转链表的一部分

206. 反转链表(简单)

在这里插入图片描述

  1. 递归解法
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        if head is None or head.next is None:
            return head
        last = self.reverseList(head.next)
        head.next.next = head
        head.next = None
        return last

这里注意basecase 当链表为空或者只有一个节点, 反转就是自身

  1. 迭代
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def reverseList(self, head: ListNode) -> ListNode:     
        prev = None
        cur = head
        while cur:
            next = cur.next
            cur.next = prev
            prev = cur
            cur = next
        return prev

prev->cur->next
注意为什么要先取得next, 其实next相当于一个temp变量, 暂时把cur的下一个节点存起来,因为接下来cur要与prev相连用于颠倒指针指向, 如果没有存next, cur将无法移动到下一个节点.
cur与prev相连之后可以把cur和prev同时向后移动一位

92.反转链表II(中等)

在这里插入图片描述

  1. 递归
    第一步 反转链表前 N 个节点
def reverseN(head, right):
    if right == 1:
        successor = head.next 
        return head, successor
    last, successor = reverseN(head.next, right-1)
    head.next.next = head
    head.next = successor
    return last, successor

1、base case 变为 n == 1,反转一个元素,就是它本身,同时要记录后驱节点。

2、刚才我们直接把 head.next 设置为 null,因为整个链表反转后原来的 head 变成了整个链表的最后一个节点。但现在 head 节点在递归反转之后不一定是最后一个节点了,所以要记录后驱 successor(第 n + 1 个节点),反转之后将 head 连接上。
第二步:

class Solution:
    def reverseBetween(self, head: ListNode, left: int, right: int) -> ListNode:
        def reverseN(head, right):
            if right == 1:
                successor = head.next 
                return head, successor
            last, successor = reverseN(head.next, right-1)
            head.next.next = head
            head.next = successor
            return last, successor

        if left == 1: 
            return reverseN(head, right)[0]

        head.next = self.reverseBetween(head.next, left-1, right-1)
        return head
  1. 迭代
    方法一:
    在这里插入图片描述
    找到pre 和 suc, 然后用上文的方法把中间反转然后连起来
class Solution:
    def reverseBetween(self, head: ListNode, left: int, right: int) -> ListNode:
        dummy = ListNode(-1)
        dummy.next = head
        p = dummy
        pos = 1
        while pos < left:
            p = p.next
            pos += 1
        # 现在p在需要被反转的头节点的前一位 pre
        pre = p
        while pos <= right + 1:
            p = p.next
            pos += 1
        # 现在p在需要被反转的链表的后一位 suc
        suc = p
        prev = suc
        cur = pre.next
        while cur is not suc:
            next = cur.next
            cur.next = prev
            prev = cur
            cur = next
        pre.next = prev
        return dummy.next

方法二: 一次遍历「穿针引线」反转链表(头插法)

方法一的缺点是:如果 left 和 right 的区域很大,恰好是链表的头节点和尾节点时,找到 left 和 right 需要遍历一次,反转它们之间的链表还需要遍历一次,虽然总的时间复杂度为 O(N),但遍历了链表 2 次,可不可以只遍历一次呢?答案是可以的。我们依然画图进行说明。
在这里插入图片描述

class Solution:
    def reverseBetween(self, head: ListNode, left: int, right: int) -> ListNode:
        dummy = ListNode(-1)
        dummy.next = head
        p = dummy
        for _ in range(left - 1):
            p = p.next
        prehead = p
        prev = prehead.next
        cur = prev.next
        for _ in range(right - left):
            next = cur.next
            cur.next = prehead.next 这里要注意
            prehead.next = cur
            prev.next = next
            cur = next
        return dummy.next

3. 如何 K 个一组反转链表

25.K个一组翻转链表(困难)

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def reverseKGroup(self, head: ListNode, k: int) -> ListNode:
        def reverseList(head: ListNode, tail: ListNode):
            prev = tail
            cur = head
            while cur != tail:
                next = cur.next
                cur.next = prev
                prev = cur
                cur = next
            newhead = prev
            newtail = head
            return newhead, newtail
        p = head
        dummy = ListNode(-1)
        dummy.next = head
        lasttail = dummy
        while head:
            for _ in range(k):
                if not head:
                    return dummy.next
                head = head.next
            nexthead = head
            curhead = lasttail.next
            newhead, newtail = reverseList(curhead, nexthead)
            lasttail.next = newhead
            lasttail = newtail
        return dummy.next

4. 如何判断回文链表

这道题的关键在于,单链表无法倒着遍历,无法使用双指针技巧。

  1. 递归方法:
    可以借鉴树的后序遍历,链表也来一个后序遍历不就行了
void traverse(TreeNode root) {
    // 前序遍历代码
    traverse(root.left);
    // 中序遍历代码
    traverse(root.right);
    // 后序遍历代码
}

链表的可以写成

void traverse(ListNode head) {
    // 前序遍历代码
    traverse(head.next);
    // 后序遍历代码
}
class Solution:
    def isPalindrome(self, head: ListNode) -> bool:
        left = head
        def traverse(right):
            if not right:
                return True
            nonlocal left
            res = traverse(right.next)
            res = res and right.val == left.val
            left = left.next
            return res
        return traverse(head)

方法二:
那么最简单的办法就是,快慢指针找到中点然后反转后面的链表, 然后比较.
有几点需要注意

  1. 链表有奇数个和偶数个时, 慢指针的位置不同
    1->2->2(slow)->1, 1->2->3(slow)->2->1. 当为奇数个的时候需要再把slow向右移动一位
  2. 下面的代码有个问题就是原链表被改动了,如何能复原链表呢. 可以看下面的代码.
class Solution:
    def isPalindrome(self, head: ListNode) -> bool:
        def reverseList(head):
            prev = None
            cur = head
            while cur:
                next = cur.next
                cur.next = prev
                prev = cur
                cur = next
            return prev
        slow = fast = head
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
        if fast: #总共有奇数 1->2->3->2->1
            slow = slow.next
        right = reverseList(slow)
        left = head
        while right:
            if right.val != left.val:
                return False
            right = right.next
            left = left.next
        return True
class Solution:
    def isPalindrome(self, head: ListNode) -> bool:
        def reverseList(head):
            prev = None
            cur = head
            while cur:
                next = cur.next
                cur.next = prev
                prev = cur
                cur = next
            return prev
        slow = fast = head
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
        if fast: #总共有奇数 1->2->3->2->1
            slow = slow.next
        righthead = reverseList(slow)# 注意区别
        right = righthead
        left = head
        while right and right.next: # 注意区别
            if right.val != left.val:
                return False
            right = right.next
            left = left.next
        if right and left.val != right.val:# 注意区别
            return False# 注意区别
        left.next = reverseList(righthead)# 注意区别
        return True
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值