今天想和大家分享两个链表操作中的经典问题:带随机指针的链表深拷贝和链表排序。这两个问题在技术面试中经常出现,也是检验链表操作基本功的试金石。我会结合自己的解题经验,详细拆解这两个问题的解决思路和实现细节。
题目要求我们完整复制一个链表,包括每个节点的值、next指针和random指针。这里的难点在于random指针可能指向链表中的任意节点,甚至是null。
常规的哈希表解法虽然直观,但需要O(n)的额外空间。这里我想分享一个更巧妙的O(1)空间解法,通过在原链表中插入复制节点的方式实现。
首先遍历原链表,在每个节点后面插入它的复制节点。例如原链表是1→2→3,复制后变为1→1'→2→2'→3→3'。
python复制cur = head
while cur:
# 创建新节点并插入到当前节点后面
new_node = Node(cur.val, cur.next)
cur.next = new_node
cur = new_node.next
这是最核心的部分。由于复制节点紧跟在原节点后面,我们可以利用这个特性设置random指针:
python复制cur = head
while cur:
if cur.random:
# 复制节点的random指向原节点random的下一个(即对应的复制节点)
cur.next.random = cur.random.next
cur = cur.next.next
最后将合并的链表拆分为原链表和复制链表:
python复制dummy = Node(0)
copy_cur = dummy
cur = head
while cur:
# 提取复制节点
copy_cur.next = cur.next
copy_cur = copy_cur.next
# 恢复原链表
cur.next = cur.next.next
cur = cur.next
注意事项:
- 处理random指针时要先检查是否为None
- 拆分链表时要同时维护原链表和复制链表的连接
- 边界情况处理:空链表、单节点链表等
链表排序与数组排序有很大不同。由于链表不能随机访问,像快速排序这样依赖随机访问的算法效率很低。而归并排序天然适合链表结构,因为:
使用快慢指针找到中间节点,并将链表一分为二:
python复制def middle(node):
slow = fast = node
pre = None # 记录中间节点的前一个节点
while fast and fast.next:
pre = slow
slow = slow.next
fast = fast.next.next
if pre:
pre.next = None # 断开链表
return slow
合并两个有序链表的经典实现:
python复制def mergeTwoList(p, q):
dummy = cur = ListNode()
while p and q:
if p.val <= q.val:
cur.next = p
p = p.next
else:
cur.next = q
q = q.next
cur = cur.next
# 将剩余部分直接连接
cur.next = p if p else q
return dummy.next
将拆分和合并组合起来实现完整的归并排序:
python复制def sortList(head):
# 递归终止条件:空链表或单节点
if not head or not head.next:
return head
# 找到中间节点并拆分
mid = middle(head)
# 递归排序两个子链表
left = sortList(head)
right = sortList(mid)
# 合并排序后的链表
return mergeTwoList(left, right)
优化建议:
- 可以改为迭代实现以避免递归的栈空间开销
- 对于小规模链表可以切换到插入排序等简单算法
- 在实际应用中可以考虑并行化处理两个子链表的排序
调试技巧:可以打印链表的结构辅助调试,特别是在处理random指针时。
调试建议:可以添加打印语句跟踪递归过程,观察链表被拆分和合并的顺序。
在实际编码中,我发现有几个关键点特别容易出错:
一个小技巧:在实现链表算法时,可以先用简单的例子手动模拟算法的执行过程,这能帮助发现很多潜在的问题。
链表操作是编程基本功,需要反复练习才能熟练掌握。建议大家可以多做一些类似的题目,比如反转链表、环形链表检测等,这些都是面试中的高频考点。