Leetcode 链表 3
题号直达: 25 K 个一组翻转链表 | 138 随机链表的复制
LeetCode 25. K 个一组翻转链表 ↑
题目
给你链表的头节点 head,每 (k) 个节点一组进行翻转,请你返回修改后的链表。
(k) 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 (k) 的整数倍,那么请将最后剩余的节点保持原有顺序。
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
提示
- 链表中的节点数目为 (n)
- (1 ≤ k ≤ n ≤ 5000)
- (0 ≤ Node.val ≤ 1000)
进阶:你可以设计一个只用 (O(1)) 额外内存空间的算法解决此问题吗?
代码(Python)
class Solution(object):
def reverseKGroup(self, head, k):
"""
:type head: Optional[ListNode]
:type k: int
:rtype: Optional[ListNode]
"""
def reverseList_correct(head, tail):
"""
反转从 head 到 tail 的链表(包括 tail)
返回 (newHead, newTail)
"""
prev = tail.next # 保存 tail 后面的节点
curr = head
last = tail.next # 记录反转后的尾节点应该指向的位置
while prev != tail:
next_temp = curr.next
curr.next = prev
prev = curr
curr = next_temp
return tail, head # tail 变成新的头,head 变成新的尾
hair = ListNode(0)
hair.next = head
pre = hair
while True:
# 找到当前组的尾节点
tail = pre
for i in range(k):
tail = tail.next
if not tail:
return hair.next
# 保存下一组的头节点
next_group_head = tail.next
# 反转当前组
newHead, newTail = reverseList_correct(head, tail)
# 连接回原链表
pre.next = newHead
newTail.next = next_group_head
# 更新指针,准备下一组
pre = newTail
head = next_group_head
思路解析
- 整体流程:用哑节点
hair作为哨兵,pre指向「当前待翻转组」的前驱。每次循环:先通过for i in range(k)找到本组的尾节点tail;若不足 (k) 个(tail为None),直接返回;否则反转[head, tail]这一段,再把反转后的子链表接回pre和下一组之间。 - 反转子链表:
reverseList_correct(head, tail)将head到tail的节点原地反转。用prev = tail.next作为反转后尾节点的next,迭代地让每个节点的next指向前驱,最后返回(tail, head)作为新的头、尾。 - 连接与更新:反转后
pre.next = newHead,newTail.next = next_group_head;然后将pre移到newTail,head移到next_group_head,进入下一组。 - 复杂度:每个节点被访问常数次,时间复杂度 (O(n));只使用常数个指针,空间复杂度 (O(1)),满足进阶要求。
LeetCode 138. 随机链表的复制 ↑
题目
给你一个长度为 (n) 的链表,每个节点包含一个额外增加的随机指针 random,该指针可以指向链表中的任何节点或空节点。
构造这个链表的深拷贝。深拷贝应该由恰好 (n) 个全新节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点。
例如,如果原链表中有 X 和 Y 两个节点,其中 X.random → Y。那么在复制链表中对应的两个节点 x 和 y,同样有 x.random → y。
返回复制链表的头节点。
提示
- (0 ≤ n ≤ 1000)
- (-10^4 ≤ Node.val ≤ 10^4)
Node.random为null或指向链表中的节点
代码(Python)解法一:递归 + 哈希表
class Solution(object):
def __init__(self):
self.cachedNode = {}
def copyRandomList(self, head):
"""
:type head: Node
:rtype: Node
"""
if head is None:
return None
if head not in self.cachedNode:
# 创建新节点
headNew = Node(head.val)
self.cachedNode[head] = headNew
# 递归复制 next 和 random 指针
headNew.next = self.copyRandomList(head.next)
headNew.random = self.copyRandomList(head.random)
return self.cachedNode[head]
思路解析(解法一)
- 核心思想:递归 + 哈希表(
cachedNode),将原节点 → 新节点一一对应。 - 先创建节点,再连接指针:进入
if head not in self.cachedNode后立刻Node(head.val)并写入缓存,再去递归填next/random,避免在「新节点尚未存在」时就引用它。 - 先存入缓存,再递归:把
cachedNode[head] = headNew放在递归之前,这样当random或next指回已访问过的原节点时,后续递归会直接return self.cachedNode[head],不会无限递归。 next与random分别递归:两条边独立复制,逻辑对称;head.random为None时copyRandomList(None)返回None,合法。- 利用缓存返回已建节点:保证每个原节点只对应一个新节点,满足深拷贝语义。
复杂度(解法一)
- 时间复杂度 (O(n)):每个原节点至多创建一次、每条边(
next/random)在递归树中对应常数次访问。 - 空间复杂度 (O(n)):哈希表存 (n) 个映射,递归栈深度 (O(n))。
示例:逐步走读代码(解法一)
设原链表为两个节点(仅示意结构):
| 原节点 | val |
next |
random |
|---|---|---|---|
| A | 1 | B | B |
| B | 2 | None |
A |
即:A → B,A.random → B,B.random → A(存在环状引用,最能体现「先缓存再递归」的必要性)。
记新节点为 A'、B',初始 cachedNode = {}。
-
copyRandomList(A)A不在缓存 → 创建 A'(值为 1),cachedNode[A] = A'。- 执行
headNew.next = copyRandomList(B)(先不填random)。
-
copyRandomList(B)B不在缓存 → 创建 B'(值为 2),cachedNode[B] = B'。headNew.next = copyRandomList(None)→ 返回None,故 B'.next = None。headNew.random = copyRandomList(A):此时 A 已在缓存,不再进入if内创建逻辑,直接return cachedNode[A] = A',故 B'.random → A'。- 返回 B'。
-
回到
copyRandomList(A)中未完成的赋值:- A'.next = B'(上一步返回值)。
headNew.random = copyRandomList(B):B 已在缓存,直接return B',故 A'.random → B'。
-
返回 A',即为复制链表的头。
结果核对:复制链为 A' → B',A'.random → B',B'.random → A',与原件结构一致,且所有指针只指向新节点。
若不做「先缓存再递归」:例如在创建 A' 后立刻 copyRandomList(B),而在处理 B 的 random 时又需要 A',若没有提前 cachedNode[A]=A',要么拿不到 A',要么会重复创建 A'',深拷贝不成立;若 random 先于 next 递归且未缓存,也可能在填 A'.random 时再次进入对 A 的复制,造成无限递归。先占位、再递归 同时解决了「环」与「重复创建」两类问题。
代码(Python)解法二:交错插入 + 拆链((O(1)) 额外空间)
class Solution(object):
def copyRandomList(self, head):
"""
:type head: Node
:rtype: Node
"""
if not head:
return None
p = head
while p != None:
p_new = Node(p.val)
p_new.next = p.next
p.next = p_new
p = p.next.next
# return head
odd = head # Original
even = head.next # New
while odd != None:
if odd.random == None:
even.random = None
else:
even.random = odd.random.next
odd = odd.next.next
if odd == None:
break
even = even.next.next
odd = head
even = head.next
ans = head.next
while odd != None:
odd.next = even.next
if even.next != None:
even.next = even.next.next
odd = odd.next
even = even.next
return ans
思路解析(解法二)
- 第一阶段(交错插入):对每个原节点
p,在其后插入拷贝p_new,使结构变为… → p → p_new → p_old_next → …。这样原节点p的下一个节点就是自己的拷贝,无需哈希表即可建立对应关系。 - 第二阶段(复制
random):odd遍历原节点(下标 0,2,4…),even为紧跟其后的新节点。若odd.random指向某原节点R,则拷贝的random应为R后面的新节点,即even.random = odd.random.next;odd.random为None时新节点random也为None。 - 第三阶段(拆链):恢复原链表的
next,同时把拷贝链连成独立链表:odd.next = even.next跳过拷贝节点;even.next = even.next.next把拷贝节点串起来。ans = head.next为拷贝链头,最后返回ans。 - 复杂度:时间 (O(n)),额外空间 (O(1))(不计输出链表),满足进阶要求。

浙公网安备 33010602011771号