给你一个链表,删除链表的倒数第 n **个结点,并且返回链表的头结点。
示例 1:

ini
输入: head = [1,2,3,4,5], n = 2
输出: [1,2,3,5]
示例 2:
ini
输入: head = [1], n = 1
输出: []
示例 3:
ini
输入: head = [1,2], n = 1
输出: [1]
提示:
- 链表中结点的数目为
sz 1 <= sz <= 300 <= Node.val <= 1001 <= n <= sz
进阶: 你能尝试使用一趟扫描实现吗?
双指针的经典应用,如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了。
思路是这样的,但要注意一些细节。
分为如下几步:
- 首先这里我推荐大家使用虚拟头结点,这样方便处理删除实际头结点的逻辑,如果虚拟头结点不清楚,可以看这篇: 链表:听说用虚拟头节点会方便很多?
- 定义fast指针和slow指针,初始值为虚拟头结点,如图:

- fast首先走n + 1步 ,为什么是n+1呢,因为只有这样同时移动的时候slow才能指向删除节点的上一个节点(方便做删除操作),如图:

- fast和slow同时移动,直到fast指向末尾,如题:
//图片中有错别词:应该将"只到"改为"直到" - 删除slow指向的下一个节点,如图:

kotlin
class Solution {
fun removeNthFromEnd(head: ListNode?, n: Int): ListNode? {
//新建一个虚拟头节点指向head
val dummyNode = ListNode(0)
dummyNode.next = head
//快慢指针指向虚拟头节点
var fastNode: ListNode? = dummyNode
var slowNode: ListNode? = dummyNode
// 只要快慢指针相差 n 个结点即可
for (i in 0..<n) {
fastNode = fastNode?.next
}
while (fastNode?.next != null) {
fastNode = fastNode.next
slowNode = slowNode?.next
}
// 此时 slowIndex 的位置就是待删除元素的前一个位置。
// 具体情况可自己画一个链表长度为 3 的图来模拟代码来理解
slowNode?.next = slowNode.next?.next
return dummyNode.next
}
}
- 时间复杂度: O(n)
- 空间复杂度: O(1)
递归解法:
java
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
// 创建一个新的哑节点,指向原链表头
ListNode s = new ListNode(-1, head);
// 递归调用remove方法,从哑节点开始进行删除操作
remove(s, n);
// 返回新链表的头(去掉可能的哑节点)
return s.next;
}
public int remove(ListNode p, int n) {
// 递归结束条件:如果当前节点为空,返回0
if (p == null) {
return 0;
}
// 递归深入到下一个节点
int net = remove(p.next, n);
// 如果当前节点是倒数第n个节点,进行删除操作
if (net == n) {
p.next = p.next.next;
}
// 返回当前节点的总深度
return net + 1;
}
}