问题描述
给定一个头节点为 head 的链表,用于记录一系列核心肌群训练项目编号,请查找并返回倒数第 cnt 个训练项目编号。
示例 1:
输入:head = [2,4,7,8], cnt = 1
输出:8
提示:
-
1 <= head.length <= 100
-
0 <= head[i] <= 100
-
1 <= cnt <= head.length
问题分析
这是一个经典的链表问题,要求找到链表中倒数第 cnt 个节点。链表的特点是只能顺序访问,不能像数组那样直接通过索引访问。因此,我们需要一种高效的算法来解决这个问题。
解决方案:快慢指针法
算法思路
使用两个指针 fast 和 slow,都初始化为头节点:
-
先让
fast指针向前移动cnt步 -
然后同时移动
fast和slow指针,直到fast到达链表末尾 -
此时
slow指针指向的就是倒数第cnt个节点
算法原理
这个算法的巧妙之处在于利用了"距离差"的原理。当 fast 指针先走 cnt 步后,fast 和 slow 之间相隔 cnt 个节点。然后两个指针以相同的速度前进,当 fast 到达链表末尾时,slow 正好落后 fast 指针 cnt 个节点,也就是位于倒数第 cnt 个位置。
代码实现
python
class Solution:
def trainingPlan(self, head: Optional[ListNode], cnt: int) -> Optional[ListNode]:
# 初始化快慢指针
fast = slow = head
# 快指针先走cnt步
while cnt > 0 and fast:
fast = fast.next
cnt -= 1
# 同时移动快慢指针,直到快指针到达末尾
while fast:
fast = fast.next
slow = slow.next
return slow
算法复杂度分析
-
时间复杂度:O(n),其中 n 是链表的长度。我们只需要遍历链表一次。
-
空间复杂度:O(1),只使用了常数级别的额外空间。
算法详解
示例演示
以链表 2 -> 4 -> 7 -> 8 和 cnt = 2 为例(找倒数第2个节点):
-
初始化:
fast = slow = 节点2 -
快指针先走2步:
-
第一步:
fast = 节点4,cnt = 1 -
第二步:
fast = 节点7,cnt = 0
-
-
同时移动两个指针:
-
fast = 节点8,slow = 节点4 -
fast = None,slow = 节点7
-
-
返回
slow(节点7,值为7)
边界情况处理
-
cnt等于链表长度 :当
cnt等于链表长度时,快指针会走到链表末尾(fast = None),慢指针仍指向头节点,正好是倒数第cnt个节点。 -
cnt等于1 :当
cnt = 1时,找到的是最后一个节点。 -
链表只有一个节点 :无论
cnt是多少(只能是1),都能正确返回该节点。
其他解法对比
解法一:两次遍历法
先遍历链表得到长度 n,然后再遍历到第 n-cnt 个节点。
python
class Solution:
def trainingPlan(self, head: Optional[ListNode], cnt: int) -> Optional[ListNode]:
# 第一次遍历:计算链表长度
length = 0
cur = head
while cur:
length += 1
cur = cur.next
# 第二次遍历:找到第length-cnt个节点
cur = head
for _ in range(length - cnt):
cur = cur.next
return cur
复杂度:时间复杂度 O(2n) = O(n),空间复杂度 O(1)。
解法二:使用栈
遍历链表,将所有节点压入栈中,然后弹出前 cnt 个节点,第 cnt 个弹出的就是目标节点。
python
class Solution:
def trainingPlan(self, head: Optional[ListNode], cnt: int) -> Optional[ListNode]:
stack = []
cur = head
while cur:
stack.append(cur)
cur = cur.next
# 弹出前cnt-1个节点
for _ in range(cnt - 1):
stack.pop()
return stack.pop()
复杂度:时间复杂度 O(n),空间复杂度 O(n)。
解法比较
| 方法 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
|---|---|---|---|---|
| 快慢指针法 | O(n) | O(1) | 效率高,空间优 | 思路需要理解 |
| 两次遍历法 | O(2n) | O(1) | 思路简单直接 | 需要两次遍历 |
| 栈方法 | O(n) | O(n) | 思路简单 | 需要额外空间 |
实际应用
这个问题在实际开发中有着广泛的应用:
-
查找中间节点:类似思路可以找到链表的中间节点
-
检测链表环:快慢指针法也可用于检测链表是否有环
-
数据库查询优化:在处理链表形式的数据时,类似算法可以提高效率
总结
快慢指针法是解决链表倒数第K个节点问题的最优解,它具有以下优点:
-
时间复杂度为 O(n),只需遍历一次链表
-
空间复杂度为 O(1),不需要额外空间
-
代码简洁,逻辑清晰
掌握这种双指针技巧对于解决链表相关问题非常有帮助,它是算法面试中的常考知识点,也是实际开发中处理链表数据的高效方法。