算法思想:
使用了双指针法。下面是详细的算法思想:
1. 引入虚拟头节点(dummy node)
- 为了处理链表的一些边界情况(比如删除头节点),我们在链表的头部引入了一个虚拟节点
dummy
,并让它指向原来的头节点head
。这样,无论我们要删除哪个节点,处理过程都变得更加统一和简单。
2. 定义两个指针:快指针(fast)和慢指针(slow)
- 我们使用两个指针,
fast
和slow
,最初都指向虚拟头节点dummy
。 - 快指针
fast
会比慢指针slow
超前移动n+1
步。这样,当fast
指向链表末尾(null
)时,slow
刚好指向要删除节点的前一个节点。
3. 移动快指针
- 首先,快指针
fast
先向前移动n+1
步,这样可以确保快指针和慢指针之间相隔n
个节点。
4. 同时移动快慢指针
- 接下来,快慢指针一起向前移动,直到快指针到达链表的末尾。这时,慢指针
slow
就刚好处于要删除节点的前一个位置。
5. 删除节点
- 现在,慢指针
slow
的下一个节点就是我们需要删除的节点。通过slow.next = slow.next.next
,我们跳过了这个节点,达到了删除的目的。
6. 返回新的头节点
- 最后,返回
dummy.next
。注意,链表的头节点可能发生了变化(如果原来的头节点被删除),因此我们返回虚拟节点dummy
的下一个节点作为新的链表头节点。
代码核心思路总结:
- 通过快慢指针法,仅需遍历链表一次(一次循环)就可以找到倒数第N个节点,并将其删除,时间复杂度为 O(L),其中 L 是链表的长度。空间复杂度为 O(1),因为只用了常数级别的额外空间。
示例分析:
假设输入链表为 [1, 2, 3, 4, 5]
,n = 2
,即删除倒数第二个节点。
- 初始化:
fast
和slow
都指向虚拟节点dummy
。 - 快指针前移:
fast
先向前移动n+1 = 3
步,指向节点3
。 - 同步移动:同时移动
fast
和slow
,直到fast
指向null
,此时slow
指向节点3
的前一个节点,即节点2
。 - 删除节点:通过
slow.next = slow.next.next
删除节点4
,最终链表变为[1, 2, 3, 5]
。
这样就成功地删除了倒数第2个节点。
java 实现代码:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummyNode = new ListNode(0);
dummyNode.next = head;
ListNode slow = dummyNode; //dummyNode,slow,fast都是引用类型
ListNode fast = dummyNode;
for(int i = 0; i <= n; i++) { //快指针先移动 n+1 步
fast = fast.next;
}
while(fast != null) { //然后快慢指针一起移动
slow = slow.next;
fast = fast.next;
}
slow.next = slow.next.next;
return dummyNode.next;
}
}