1. LeetCode题目知识点解析(25、61、82)
最近在刷LeetCode时遇到了几道很有意思的链表题目,分别是25题(K个一组翻转链表)、61题(旋转链表)和82题(删除排序链表中的重复元素II)。这三道题看似简单,但实际写起来发现有不少细节需要注意。今天就把我在解题过程中总结的知识点和踩过的坑分享给大家。
链表操作是算法面试中的常客,这三道题覆盖了链表反转、节点位移和重复处理等典型场景。掌握这些基础操作,对处理更复杂的链表问题会有很大帮助。下面我会逐题分析解题思路和实现细节。
2. 题目25:K个一组翻转链表
2.1 问题描述与核心思路
给定一个链表,每k个节点一组进行翻转,返回翻转后的链表。如果节点总数不是k的整数倍,最后剩余的节点保持原有顺序。
这道题的核心在于如何高效地分段反转链表。我的思路是:
- 先计算链表长度,确定需要反转的组数
- 对每一组进行标准的链表反转操作
- 处理好组与组之间的连接关系
特别注意:当剩余节点不足k个时,要保持原样不反转
2.2 关键实现细节
python复制def reverseKGroup(head, k):
def reverse(head, tail):
# 标准链表反转实现
prev = tail.next
curr = head
while prev != tail:
next_node = curr.next
curr.next = prev
prev = curr
curr = next_node
return tail, head
dummy = ListNode(0)
dummy.next = head
pre = dummy
while head:
tail = pre
# 检查剩余长度是否足够
for _ in range(k):
tail = tail.next
if not tail:
return dummy.next
# 记录下一组的起点
next_group = tail.next
# 反转当前组
head, tail = reverse(head, tail)
# 重新连接链表
pre.next = head
tail.next = next_group
# 移动指针到下一组
pre = tail
head = tail.next
return dummy.next
2.3 常见错误与调试技巧
-
边界条件处理:当k=1时,相当于不反转,直接返回原链表。很多同学会忘记这个特殊情况。
-
指针移动顺序:反转后pre指针应该移动到新组的尾部(即原组的头部),而不是直接跳到next_group。
-
内存泄漏:在C++实现中,要注意临时节点的释放,避免内存泄漏。
3. 题目61:旋转链表
3.1 问题分析与解法选择
给定一个链表,将链表向右旋转k个位置。例如:
输入:1->2->3->4->5->NULL, k=2
输出:4->5->1->2->3->NULL
这道题的关键点在于:
- 实际旋转次数k可能大于链表长度,需要取模
- 找到新的尾节点和头节点
- 重新连接链表
3.2 最优解实现
python复制def rotateRight(head, k):
if not head or not head.next or k == 0:
return head
# 计算链表长度并找到尾节点
length = 1
tail = head
while tail.next:
tail = tail.next
length += 1
# 计算实际需要旋转的次数
k = k % length
if k == 0:
return head
# 找到新的尾节点(第length - k个节点)
new_tail = head
for _ in range(length - k - 1):
new_tail = new_tail.next
# 重新连接链表
new_head = new_tail.next
new_tail.next = None
tail.next = head
return new_head
3.3 性能优化与注意事项
-
提前终止:当k=0或链表长度≤1时直接返回,避免不必要的计算。
-
取模运算:k可能非常大,必须对链表长度取模,否则会超时。
-
指针移动:找新尾节点时,循环次数是length-k-1,不是length-k,容易出错。
4. 题目82:删除排序链表中的重复元素II
4.1 问题重述与解题策略
给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中没有重复出现的数字。
与简单版本(保留一个重复节点)不同,这道题要求删除所有重复节点。我的解决思路:
- 使用哑节点简化头节点处理
- 双指针法:pre指向当前确定不重复的节点,cur用于遍历
- 发现重复时,跳过所有重复节点
4.2 代码实现与注释
python复制def deleteDuplicates(head):
dummy = ListNode(0)
dummy.next = head
pre = dummy
cur = head
while cur:
# 发现重复节点
if cur.next and cur.val == cur.next.val:
# 跳过所有重复节点
while cur.next and cur.val == cur.next.val:
cur = cur.next
pre.next = cur.next
else:
pre = pre.next
cur = cur.next
return dummy.next
4.4 易错点与调试经验
-
哑节点的使用:不引入哑节点时,头节点重复的情况需要特殊处理。
-
指针移动逻辑:只有在确认当前节点不重复时,才移动pre指针,否则只移动cur指针。
-
边界条件:空链表、全重复链表、头节点重复等情况都需要测试。
5. 链表问题通用技巧
通过这三道题的练习,我总结了一些链表问题的通用解法:
-
哑节点(dummy node):简化头节点的特殊处理,避免空指针异常。
-
双指针法:快慢指针、前后指针等技巧在链表问题中非常实用。
-
画图辅助:在纸上画出链表和指针变化,能帮助理清思路。
-
边界测试:空链表、单节点链表、全重复链表等边界情况要重点测试。
-
循环不变式:明确循环中每个指针的含义和不变式,避免逻辑混乱。
在实际面试中,建议先和面试官确认输入输出的具体要求,解释清楚思路后再开始编码。链表问题往往代码不长,但细节决定成败,需要格外小心指针操作和边界条件。