1. 链表反转

1.1 完全反转

剑指 Offer 24. 反转链表

  • 迭代
class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        p = head
        pre = None
        while p:
            aft = p.next
            p.next = pre
            pre = p
            p = aft
        return pre
  • 递归

递归法就记一张图 — 递归反转链表的一部分

1->2->3->^
1->2<-3
   |->^
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

1.2 部分反转

92. 反转链表 II

结合对1.1递归法的学习,将后继None替换成本场景的successor,我们看看commits:

【科学刷题】完全吃透所有链表题_递归

class Solution:
    successor = None

    def reverseN(self, head: ListNode, N=inf) -> ListNode:
        if N == 1 or head is None or head.next is None:
            self.successor = head.next
            return head
        last = self.reverseN(head.next, N - 1)
        head.next.next = head
        head.next = self.successor
        return last

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

1.3 K个一组反转

25. K 个一组翻转链表

class Solution:
    def reverse(self, a, b):
        # 相当于左开右闭,例如k=2,
        #  1->2->3
        # a↑    b↑
        # 最后返回是的 2->1->∅
        pre = None
        p = a
        while p != b:
            aft = p.next
            p.next = pre
            pre = p
            p = aft
        return pre

    def reverseKGroup(self, head: ListNode, k: int) -> ListNode:
        a = b = head
        # 循环体写错 (老错误)
        for _ in range(k):
            if b is None:
                return head
            b = b.next
        new_head = self.reverse(a, b)
        # 把 a 写成了 new_head
        a.next = self.reverseKGroup(b, k) # b 写错 (老错误)
        return new_head
2. 链表相交 + 链表找环

本文还有大量没有考虑到的情况,可以看相关博客

数据结构及算法:链表相关(如何判断单链表是否存在环、找出入环点、链表相交等问题)

寻找两个链表相交节点方法(可以是有环链表)

求两个链表的相交点(含链表带环情况)

【科学刷题】完全吃透所有链表题_重复元素_02插图出自"求两个链表的相交点(含链表带环情况)",本文2.1 普通相交讨论case 0,2.2 普通有环讨论case 1,2.3 相交有环讨论case 2, case 3 。2.3 相交有环对4种情况都做了总结,考虑了所有边界条件

2.1 普通相交

160. 相交链表

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

1650. 二叉树的最近公共祖先 III

class Solution:
    def lowestCommonAncestor(self, p: 'Node', q: 'Node') -> 'Node':
        a, b = p, q
        while a != b:
            a = a.parent if a else q
            b = b.parent if b else p
        return a

2.2 普通有环

2.2.1 判断是否有环

141. 环形链表

class Solution:
    def hasCycle(self, head: ListNode) -> bool:
        slow = fast = head
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
            if slow == fast:
                return True
        return False

2.2.2 找出环的入口

142. 环形链表 II

如果快慢指针是在写不出来,可以用hash表来写,就是空间复杂度会变成 O ( N ) O(N) O(N)

class Solution:
    def detectCycle(self, head: ListNode) -> ListNode:
        vis = {}
        p = head
        while p:
            id_ = id(p)
            if id_ in vis:
                return vis[id_]
            vis[id_] = p
            p = p.next
        return None

快慢指针

class Solution:
    def detectCycle(self, head: ListNode) -> ListNode:
        slow = fast = head
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
            if slow == fast:
                break
        if fast and fast.next:
            slow = head
            while slow != fast:
                slow = slow.next
                fast = fast.next
            return slow
        return None

2.3 相交有环

【科学刷题】完全吃透所有链表题_重复元素_02

本题LeetCode没有,笔者结合博客插图的测试用例,设计了4个单元测试

  • 核心代码
class ListNode:
    '''链表结点定义'''

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


def getIntersectionNode(headA: ListNode, headB: ListNode) -> ListNode:
    '''headA,headB均无环时,求两个链表的交点(如果没有交点,返回None)'''
    a, b = headA, headB
    while a != b:
        a = a.next if a else headB
        b = b.next if b else headA
    return a


def detectCycle(head: ListNode) -> ListNode:
    '''找环入口点函数'''
    slow = fast = head
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
        if slow == fast:
            break
    if fast and fast.next:
        slow = head
        while slow != fast:
            slow = slow.next
            fast = fast.next
        return slow
    return None


def detectIntersect(headA: ListNode, headB: ListNode) -> ListNode:
    '''找到headA与headB的交点,考虑多种边界条件'''
    if headA is None or headB is None:
        return None
    entry1 = detectCycle(headA)
    entry2 = detectCycle(headB)
    if entry1 is None and entry2 is None:
        return getIntersectionNode(headA, headB)
    if entry1 is None or entry2 is None:
        return None
    # 环外相交
    if entry1 == entry2:
        # 临时将环解开,然后用【普通链表相交】的方法做就行了
        entry1_next = entry1.next
        entry1.next = None
        ret = getIntersectionNode(headA, headB)
        entry1.next = entry1_next
        return ret
    # 环内相交
    else:
        # 返回任一相交结点即可
        return entry1
  • 测试代码
# =============================
# ==========测试代码============
# =============================

def get_nodes():
    ret = {}
    for i in list(range(1, 11)) + list(range(11, 56, 11)):
        ret[i] = ListNode(i)
    return ret


def test_0():
    '''两个链表都不带环'''
    nodes = get_nodes()
    for i in range(1, 10):
        nodes[i].next = nodes[i + 1]
    for i in range(11, 45, 11):
        nodes[i].next = nodes[i + 11]
    nodes[55].next = nodes[4]
    assert detectIntersect(nodes[1], nodes[11]).val == 4


def test_1():
    '''两个链表其中一个带环'''
    nodes = get_nodes()
    for i in range(1, 10):
        nodes[i].next = nodes[i + 1]
    nodes[10].next = nodes[5]
    for i in range(11, 34, 11):
        nodes[i].next = nodes[i + 11]
    assert detectIntersect(nodes[1], nodes[11]) is None


def test_2():
    '''两个都带环,交点在环外'''
    nodes = get_nodes()
    for i in range(1, 10):
        nodes[i].next = nodes[i + 1]
    nodes[10].next = nodes[5]
    for i in range(11, 34, 11):
        nodes[i].next = nodes[i + 11]
    nodes[33].next = nodes[4]
    assert detectIntersect(nodes[1], nodes[11]).val == 4


def test_3():
    '''两个都带环,交点在环内'''
    nodes = get_nodes()
    for i in range(1, 10):
        nodes[i].next = nodes[i + 1]
    nodes[10].next = nodes[3]
    for i in range(11, 34, 11):
        nodes[i].next = nodes[i + 11]
    nodes[33].next = nodes[4]
    ret = detectIntersect(nodes[1], nodes[11]).val
    # print(ret)
    assert ret in [3, 4]


if __name__ == '__main__':
    test_0()
    test_1()
    test_2()
    test_3()
3 删除排序链表中的重复元素

3.1 使每个元素只出现一次

83. 删除排序链表中的重复元素

【科学刷题】完全吃透所有链表题_递归_04

class Solution:
    def deleteDuplicates(self, head: ListNode) -> ListNode:
        slow = fast = head
        while fast:
            while fast and slow.val == fast.val:
                fast = fast.next
            slow.next = fast
            slow = fast
        return head

3.2 删除全部重复元素

82. 删除排序链表中的重复元素 II

【科学刷题】完全吃透所有链表题_重复元素_05

在草稿纸上推一下测试用例,只要能想明白要用.next.val做比较,就不难理解了。

class Solution:
    def deleteDuplicates(self, head: ListNode) -> ListNode:
        # 结点数<=1直接返回
        if not (head and head.next): return head
        dummy = ListNode(-inf)
        dummy.next = head
        a = dummy
        b = head
        while b and b.next:
            if a.next.val == b.next.val:  # 忘了写对 .val 的判断
                while b and b.next and a.next.val == b.next.val:
                    b = b.next
                a.next = b.next
                b = b.next
            else:
                a = a.next
                b = b.next
        return dummy.next
4 链表重排

4.1 两两交换链表中的节点

24. 两两交换链表中的节点

【科学刷题】完全吃透所有链表题_重复元素_06

  • 首先在草稿纸上画出上图
    • 因为需要保证p1, p2存在,故while条件是dp.next and dp.next.next
    • 做完之后,p1成为新的dp
class Solution:
    def swapPairs(self, head: ListNode) -> ListNode:
        dummy = ListNode(0)
        dummy.next = head
        dp = dummy
        while dp.next and dp.next.next:
            p1 = dp.next
            p2 = dp.next.next
            dp.next = p2
            p1.next = p2.next
            p2.next = p1
            dp = p1
        return dummy.next

4.2 重排链表 — 中点 + 反转 + 交替

143. 重排链表

这道题反过来就是字节的一个题了

def reverse(head: ListNode):
    p=head
    pre=None
    while p:
        aft=p.next
        p.next=pre
        pre=p
        p=aft
    return pre

class Solution:
    def reorderList(self, head: ListNode) -> None:
        if head is None or head.next is None: # 边界
            return head
        slow=head
        fast=head.next
        while fast and fast.next:
            fast=fast.next.next
            slow=slow.next
        p2=slow.next
        if p2 is None: # 边界
            return head
        slow.next=None
        p2=reverse(p2)
        a=head
        b=p2
        while a and b:
            an=a.next
            bn=b.next
            a.next=b
            b.next=an
            a=an
            b=bn
        return head

4.3 字节跳动高频题——排序奇升偶降链表

字节跳动高频题——排序奇升偶降链表

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

class Solution:    
    def sortOddEvenList(self,head):     
        if not head or not head.next:      
            return head        
        oddList,evenList = self.partition(head)        
        evenList = self.reverse(evenList)        
        return self.merge(oddList,evenList)    

    def partition(self, head: ListNode) -> ListNode:        
        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 = None        
        return [head,evenHead]    
    def reverse(self,head):        
        dumpy = ListNode(-1)        
        p = head        
        while p:            
            temp = p.next            
            p.next = dumpy.next            
            dumpy.next = p            
            p = temp        
        return dumpy.next    
    def merge(self,p,q):        
        head = ListNode(-1)        
        r = head        
        while p and q:            
            if p.val <= q.val:               
                r.next = p                
                p = p.next            
            else:                
                r.next = q                
                q = q.next            
            r = r.next        
        if p:            
            r.next = p        
        if q:            
            r.next = q        
        return head.next

感觉交替切分链表不太熟,练了下

def partition(head: ListNode):
    if not head or not head.next:
        return head
    p1, p2 = head, head.next
    head1, head2 = p1, p2
    # p2 后面至少有1个结点
    while p2 and p2.next:
        p1.next = p2.next
        p1 = p1.next
        p2.next = p1.next
        p2 = p2.next
    p1.next = None
    return head1, head2
5 链表递归

5.1 链表部分反转 + K个一组反转

92. 反转链表 II

class Solution:
    successor=None

    def reverseN(self,head, n):
        if n==1:
            self.successor=head.next
            return head
        last=self.reverseN(head.next,n-1)
        head.next.next=head
        head.next=self.successor
        return last

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

25. K 个一组翻转链表

class Solution:
    def reverse(self, a, b):
        # 相当于左开右闭,例如k=2,
        #  1->2->3
        # a↑    b↑
        # 最后返回是的 2->1->∅
        pre = None
        p = a
        while p != b:
            aft = p.next
            p.next = pre
            pre = p
            p = aft
        return pre

    def reverseKGroup(self, head: ListNode, k: int) -> ListNode:
        a = b = head
        # 循环体写错 (老错误)
        for _ in range(k):
            if b is None:
                return head
            b = b.next
        new_head = self.reverse(a, b)
        # 把 a 写成了 new_head
        a.next = self.reverseKGroup(b, k) # b 写错 (老错误)
        return new_head

5.2 回文链表

234. 回文链表

  • 递归
class Solution:

    def recur(self,left):
        if left is None:
            return True
        ans=self.recur(left.next)
        ans &= (left.val==self.right.val)
        self.right=self.right.next
        return ans

    def isPalindrome(self, head: ListNode) -> bool:
        self.right=head
        return self.recur(head)
  • 快慢+翻转
def reverse(head):
    if head is None or head.next is None:
        return head
    last=reverse(head.next)
    head.next.next=head
    head.next=None
    return last

class Solution:

    def isPalindrome(self, head: ListNode) -> bool:
        if head is None or head.next is None:
            return True
        slow=head
        fast=head.next
        while fast and fast.next:
            slow=slow.next
            fast=fast.next.next
        p2=slow.next
        slow.next=None
        p2=reverse(p2)
        p1=head
        while p1 and p2:
            if p1.val !=p2.val:
                return False
            p1=p1.next
            p2=p2.next
        return True
6 链表排序

6.1 合并K个升序链表

23. 合并K个升序链表

方法 暴力 分治
时间复杂度 O ( k 2 n ) O(k^2n) O(k2n) O ( k log ⁡ k n ) O(k\log kn) O(klogkn) O ( k log ⁡ k n ) O(k\log kn) O(klogkn)
空间复杂度 O ( 1 ) O(1) O(1) O ( log ⁡ k ) O(\log k) O(logk) O ( n ) O(n) O(n)
def merge(a: ListNode, b: ListNode):
    dummy = ListNode(0)
    dp = dummy
    while a and b:
        if a.val < b.val:
            dp.next = a
            a = a.next
        else:
            dp.next = b
            b = b.next
        dp = dp.next
    dp.next = a if a else b
    return dummy.next


def partition(lists):
    n = len(lists)
    if n == 0:
        return None
    elif n == 1:
        return lists[0]
    else:
        mid = len(lists) // 2
        return merge(partition(lists[:mid]), partition(lists[mid:]))


class Solution:
    def mergeKLists(self, lists: List[ListNode]) -> ListNode:
        return partition(lists)

6.2 链表插入排序

官方题解

147. 对链表进行插入排序

class Solution:
    def sortList(self, head: ListNode) -> ListNode:
        if not head: return head
        # 有序区 头指针
        dummy = ListNode(-inf)
        dummy.next = head
        # 有序区的尾指针,后继为None
        tail = head
        p = head.next # 无序区 指针
        tail.next = None
        while p: # 在无序区中遍历
            aft = p.next # 后继
            # 放置在有序区最后面
            if p.val >= tail.val:
                tail.next = p
                tail = p
                tail.next = None
            # 插入进有序区
            else:
                dp = dummy
                while dp.next and dp.next.val < p.val:
                    dp = dp.next
                p.next = dp.next
                dp.next = p
            p = aft # 进入后继
        return dummy.next

6.3 链表归并排序

148. 排序链表

merge函数参考 merge-two-sorted-lists

class Solution:
    def sortList(self, head: ListNode) -> ListNode:
        if head is None or head.next is None:
            return head
        slow = head
        fast = head.next
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
        mid = slow.next
        slow.next = None
        A = self.sortList(head)
        B = self.sortList(mid)
        dummy = ListNode(0)
        dp = dummy
        while A and B:
            if A.val < B.val:
                dp.next = A
                A = A.next
            else:
                dp.next = B
                B = B.next
            dp = dp.next
        dp.next = A if A else B
        return dummy.next