链表

链表是空节点,或者有一个值和一个指向下一个链表的指针,因此很多链表问题可以用递归来处理。

1. 找出两个链表的交点

例如以下示例中 A 和 B 两个链表相交于 c1:

A:          a1 → a2
                    ↘
                      c1 → c2 → c3
                    ↗
B:    b1 → b2 → b3

但是不会出现以下相交的情况,因为每个节点只有一个 next 指针,也就只能有一个后继节点,而以下示例中节点 c 有两个后继节点。

A:          a1 → a2       d1 → d2
                    ↘  ↗
                      c
                    ↗  ↘
B:    b1 → b2 → b3        e1 → e2

要求时间复杂度为 O(N),空间复杂度为 O(1)。如果不存在交点则返回 null。

设 A 的长度为 a + c,B 的长度为 b + c,其中 c 为尾部公共部分长度,可知 a + c + b = b + c + a。

当访问 A 链表的指针访问到链表尾部时,令它从链表 B 的头部开始访问链表 B;同样地,当访问 B 链表的指针访问到链表尾部时,令它从链表 A 的头部开始访问链表 A。这样就能控制访问 A 和 B 两个链表的指针能同时访问到交点。

如果不存在交点,那么 a + b = b + a,以下实现代码中 l1 和 l2 会同时为 null,从而退出循环。

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

如果只是判断是否存在交点,那么就是另一个问题,即 编程之美 3.6 的问题。有两种解法:

  • 把第一个链表的结尾连接到第二个链表的开头,看第二个链表是否存在环;
  • 或者直接比较两个链表的最后一个节点是否相同。

2.1 链表反转

反转一个单链表。

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

递归法:

处理的技巧是 “不要跳进递归,而是利用明确的定义来实现算法逻辑”。

  1. base casehead.next == None ,如果链表只有一个节点的时候反转也是它自己,直接返回即可。
  2. 当链表递归反转之后,新的头结点是 last,而之前的 head 变成了最后一个节点,同时链表的末尾要指向 None。
class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        if head == None or head.next == None:
            return head
        cur = self.reverseList(head.next)
        head.next.next = head  # 将本节点的下一个结点指向自己
        head.next = None  # 本节点指向空,当回溯时本节点便会指向前一个节点,而原本的头节点便会指向空
        return cur  # 每次递归结束返回的都是同一个尾结点

迭代法:

class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        pre = None
        cur = head
        while cur:
            nex = cur.next
            cur.next = pre
            pre = cur
            cur = nex
        return pre

2.2 反转链表前 N 个节点

  1. base case 变为 n == 1,反转一个元素,就是它本身,同时要记录后驱节点
  2. 链表反转直接把 head.next 设置为 None,因为整个链表反转后原来的 head 变成了整个链表的最后一个节点。但现在 head 节点在递归反转之后不一定是最后一个节点了,所以要记录后驱 successor(第 n + 1 个节点),反转之后将 head 连接上
def reverseN(head, n):  # 反转以 head 为起点的 n 个节点,返回新的头节点
    if n == 1:
        successor = head.next  # 记录第 n + 1 个节点
        return head
    last = reverseN(head.next, n - 1)  # 以 head.next 为起点,需要反转前 n - 1 个节点
    
    head.next.next = head
    head.next = successor  # 让反转之后的 head 节点和后面的节点连起来
    return last

2.3 反转链表的一部分

反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。

说明: 1 ≤ m ≤ n ≤ 链表长度。注意这里的索引是从 1 开始的

输入: 1->2->3->4->5->NULL, m = 2, n = 4
输出: 1->4->3->2->5->NULL
  1. 如果 m == 1,就相当于反转链表开头的 n 个元素,也就是上一题
  2. 如果 m != 1,前进到反转的起点触发 base case,将问题转化为反转前 n 个节点
class Solution:
    def __init__(self):
        self.successor = None
        
    def reverseBetween(self, head: ListNode, m: int, n: int) -> ListNode:
        # base case
        if m == 1:
            # 相当于反转前 n 个元素
            return self.reverseN(head, n)
        
        # 前进到反转的起点触发 base case,将问题转化为反转前 n 个节点
        head.next = self.reverseBetween(head.next, m - 1, n - 1)
        return head  # 若 m != 1,反转部分节点之后的头节点仍然是 head
    
    def reverseN(self, head, n):  # 反转以 head 为起点的 n 个节点,返回新的头节点
        if n == 1:
            self.successor = head.next  # 记录第 n + 1 个节点
            return head
        last = self.reverseN(head.next, n - 1)  # 以 head.next 为起点,需要反转前 n - 1 个节点
        
        head.next.next = head
        head.next = self.successor  # 让反转之后的 head 节点和后面的节点连起来
        return last

2.4 k 个一组反转链表

给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。
k 是一个正整数,它的值小于或等于链表的长度。
如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

给你这个链表:1->2->3->4->5
当 k = 2 时,应当返回: 2->1->4->3->5
当 k = 3 时,应当返回: 3->2->1->4->5

递归性质:

  • 链表是一种兼具递归和迭代性质的数据结构。当我们以 2 个节点为一组反转链表之后,后面的节点仍然是一条链表,并且规模 (长度) 比原来的链表要小,这就是子问题

算法流程:

  1. 先反转以 head 开头的 k 个元素
  2. 将第 k + 1 个元素作为 head 递归调用 reverseKGroup 函数
  3. 将上述两个过程的结果连接起来

1. 迭代反转链表:

def reverse(a):
    pre, cur, nxt = None, a, a
    while cur != None:
        nxt = cur.next  # 存储下一个节点
        cur.next = pre  # 逐个节点反转,将当前节点的 next 指向上一个节点
        pre = cur  # 更新 pre 指针位置
        cur = nxt  # 更新 cur 节点位置
    return pre

2. 反转部分链表,即反转区间 [a, b) 的元素,左闭右开:

def reverse(a, b):
    pre, cur, nxt = None, a, a
    while cur != b:
        nxt = cur.next
        cur.next = pre
        pre = cur
        cur = nxt
    # 返回反转后的头节点
    return pre

3. 实现 k 个一组反转链表:

class Solution:
    def reverseKGroup(self, head: ListNode, k: int) -> ListNode:
        if head == None: return None
        a, b = head, head
        for i in range(k):
            if b == None:  # 不足 k 个,不需要反转,base case
                return head
            b = b.next
            
        new_head = self.reverse(a, b)  # 反转前 k 个元素
        a.next = self.reverseKGroup(b, k)  # 递归反转后续链表并连接起来
        return new_head

    # 反转区间 [a, b) 的元素,左闭右开
    def reverse(self, a, b):
        pre, cur, nxt = None, a, a
        while cur != b:
            nxt = cur.next
            cur.next = pre
            pre = cur
            cur = nxt
        return pre  # 返回反转后的头节点

最终递归完成之后的样子:

3. 归并两个有序的链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
  • 递归法:两个链表头部值较小的一个节点与剩下元素的 merge 操作结果合并。
class Solution:
    def mergeTwoLists(self, l1, l2):
        if l1 is None:
            return l2
        elif l2 is None:
            return l1
        elif l1.val < l2.val:
            l1.next = self.mergeTwoLists(l1.next, l2)
            return l1
        else:
            l2.next = self.mergeTwoLists(l1, l2.next)
            return l2
  • 迭代法:
class Solution:
    def mergeTwoLists(self, l1, l2):
        prehead = ListNode(-1)

        prev = prehead
        while l1 and l2:
            if l1.val <= l2.val:
                prev.next = l1
                l1 = l1.next
            else:
                prev.next = l2
                l2 = l2.next            
            prev = prev.next

        # 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可
        prev.next = l1 if l1 is not None else l2

        return prehead.next

4. 从有序链表中删除重复节点

给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。

输入: 1->1->2
输出: 1->2
class Solution:
    def deleteDuplicates(self, head: ListNode) -> ListNode:
        if not head or not head.next:
             return head
        head.next = self.deleteDuplicates(head.next)
        return head.next if head.val == head.next.val else head
class Solution:
    def deleteDuplicates(self, head: ListNode) -> ListNode:
        if not head:
            return None
        slow, fast = head, head.next
        while fast != None:
            if fast.val != slow.val:
                slow.next = fast  # nums[slow] = nums[fast]
                slow = slow.next  # slow += 1
            fast = fast.next
        slow.next = None  # 断开与后面重复元素的连接
        return head

5. 删除链表的倒数第 n 个节点

给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。

给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.
class Solution:
    def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
        fast = head
        while n > 0:
            fast = fast.next
            n -= 1
        if not fast: return head.next
        slow = head
        while fast.next:
            fast = fast.next
            slow = slow.next
        slow.next = slow.next.next
        return head

6. 交换链表中的相邻节点

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

给定 1->2->3->4, 你应该返回 2->1->4->3.
class Solution:
    def swapPairs(self, head: ListNode) -> ListNode:
        if not head or not head.next:   # 如果链表没有节点或只有一个节点
            return head
        # 要交换的节点
        first_node = head
        second_node = head.next
        # 交换节点
        first_node.next = self.swapPairs(second_node.next)
        second_node.next = first_node
        # 头节点为 second_node
        return second_node
class Solution:
    def swapPairs(self, head: ListNode) -> ListNode:
        dummy = ListNode(-1)
        dummy.next = head

        prev_node = dummy

        while head and head.next:
            # 要交换的节点
            first_node = head;
            second_node = head.next;
            # 交换
            prev_node.next = second_node
            first_node.next = second_node.next
            second_node.next = first_node
            # 为下一次交换重新初始化 head 和 prev_node
            prev_node = first_node
            head = first_node.next
        # 返回新的头节点
        return dummy.next

7. 链表求和

给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。

你可以假设除了数字 0 之外,这两个数字都不会以零开头。

输入:(7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 8 -> 0 -> 7
class Solution:
    def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
        s1, s2 = [], []
        while l1:
            s1.append(l1.val)
            l1 = l1.next
        while l2:
            s2.append(l2.val)
            l2 = l2.next
        
        ans = None
        carry = 0
        while s1 or s2 or carry != 0:
            a = 0 if not s1 else s1.pop()
            b = 0 if not s2 else s2.pop()
            cur = a + b + carry
            carry = cur // 10
            cur = cur % 10
            cur_node = ListNode(cur)
            cur_node.next = ans
            ans = cur_node
        return ans

8. 回文链表

def isPalindrome(self, head: ListNode) -> bool:
    vals = []
    current_node = head
    while current_node is not None:
        vals.append(current_node.val)
        current_node = current_node.next
    return vals == vals[::-1]

9. 分隔链表

给定一个头节点为 root 的链表, 编写一个函数以将链表分隔为 k 个连续的部分。

每部分的长度应该尽可能的相等: 任意两部分的长度差距不能超过 1,也就是说可能有些部分为 null。

这k个部分应该按照在链表中出现的顺序进行输出,并且排在前面的部分的长度应该大于或等于后面的长度。

返回一个符合上述规则的链表的列表。

输入: 
root = [1, 2, 3], k = 5
输出: [[1],[2],[3],[],[]]
解释:
输入输出各部分都应该是链表,而不是数组。
class Solution:
    def splitListToParts(self, root: ListNode, k: int) -> List[ListNode]:
        length = 0
        cur = root
        while cur:
            length += 1
            cur = cur.next
        
        mod = length % k
        size = length // k
        ans = []
        cur = root
        for i in range(k):
            head = write = ListNode(None)
            for j in range(size + (i < mod)):
                write.next = ListNode(cur.val)
                write = write.next
                if cur: 
                    cur = cur.next
            ans.append(head.next)
        return ans

直接拆分原链表

class Solution:
    def splitListToParts(self, root: ListNode, k: int) -> List[ListNode]:
        length = 0
        cur = root
        while cur:
            length += 1
            cur = cur.next
        
        mod = length % k
        size = length // k
        ans = []
        cur = root
        for i in range(k):
            head = cur
            for j in range(size + (i < mod) - 1):
                if cur: cur = cur.next
            if cur:
                cur.next, cur = None, cur.next
            ans.append(head)
        return ans

10. 链表元素奇偶聚集

给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。

请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。

输入: 2->1->3->5->6->4->7->NULL 
输出: 2->3->6->7->1->5->4->NULL

将奇节点放在一个链表里,偶链表放在另一个链表里。然后把偶链表接在奇链表的尾部。

class Solution:
    def oddEvenList(self, head: ListNode) -> ListNode:
        if not head: return None
        odd = head
        even = head.next
        even_head = even
        while even and even.next:
            odd.next = even.next
            odd = odd.next
            even.next = odd.next
            even = even.next
        odd.next = even_head
        return head
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值