时间:2025-02-26 23:25
人气:
作者:admin
想象你在一个漫长的队伍中,想知道自己距离队尾还有多少人。一个巧妙的方法是:让你的朋友从你所在位置往后数N步,然后你和朋友一起向后走。当朋友走到队尾时,你的位置就正好是倒数第N个。这个生活中的小技巧,正是我们今天要探讨的链表算法的灵感来源。
LeetCode第19题"删除链表的倒数第N个结点"要求:给你一个链表的头节点 head 和一个整数 n ,请你删除链表的倒数第 n 个结点,并且返回链表的头结点。
例如:
输入:1 → 2 → 3 → 4 → 5, n = 2
输出:1 → 2 → 3 → 5
解释:删除倒数第2个节点(值为4)
输入:1 → 2, n = 2
输出:2
解释:删除倒数第2个节点(值为1)
输入:1, n = 1
输出:空链表
解释:删除唯一的节点
最直观的解法是先遍历一遍链表得到长度,然后再遍历一次删除目标节点:
public ListNode removeNthFromEnd(ListNode head, int n) {
// 第一次遍历,计算链表长度
int length = 0;
ListNode current = head;
while (current != null) {
length++;
current = current.next;
}
// 如果要删除的是头节点
if (length == n) {
return head.next;
}
// 找到待删除节点的前一个节点
current = head;
for (int i = 0; i < length - n - 1; i++) {
current = current.next;
}
// 执行删除操作
current.next = current.next.next;
return head;
}
就像我们在队伍中的例子,我们可以用快慢指针在一次遍历内解决问题:
public ListNode removeNthFromEnd(ListNode head, int n) {
// 创建哨兵节点,统一处理头节点的删除
ListNode dummy = new ListNode(0);
dummy.next = head;
// 初始化快慢指针
ListNode fast = dummy;
ListNode slow = dummy;
// 快指针先走n+1步(多走一步是为了让慢指针停在待删除节点的前一个位置)
for (int i = 0; i <= n; i++) {
fast = fast.next;
}
// 快慢指针同步移动
while (fast != null) {
fast = fast.next;
slow = slow.next;
}
// 执行删除操作
slow.next = slow.next.next;
return dummy.next;
}
例子:删除倒数第2个节点
1) 初始状态:
dummy → 1 → 2 → 3 → 4 → 5
S,F
2) 快指针先走n+1步:
dummy → 1 → 2 → 3 → 4 → 5
S F
3) 同步移动直到快指针到末尾:
dummy → 1 → 2 → 3 → 4 → 5
S F
4) 删除slow.next节点:
dummy → 1 → 2 → 3 → 5
为什么这个方法能工作?让我们仔细分析:
两次遍历法:
快慢指针法:
哨兵节点的使用
快慢指针的设计
边界情况的处理
这种快慢指针的思想在实际开发中有很多应用:
通过这个问题,我们学到了:
当我们遇到类似的"倒数"问题时,可以考虑:
记住:有时看似复杂的问题,用合适的思维方式就能找到优雅的解决方案。
作者:忍者算法
公众号:忍者算法
我准备了一份刷题清单,以及这些题目的详细题解,覆盖了绝大部分常见面试题。我可以很负责任地说,只要你把这些题真正掌握了,80%的算法面试都能遇到相似题目。公众号回复【刷题清单】获取~
下一篇:详解蒙哥马利算法