19. 删除链表的倒数第N个节点
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
示例:
给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.
说明:
给定的 n 保证是有效的。
进阶:
你能尝试使用一趟扫描实现吗?
思路:
快慢指针,慢指针先走n步,随后慢指针和快指针同时往后遍历,遍历到快指针为空时慢指针所指元素即使倒数第 n 个结点。
剑指offer 14. 链表中倒数第 k 个结点的要求与本题几乎一模一样,但是这题是要删除倒数第 n 个结点,而不是直接返回倒数 第 n个结点,所以慢指针移动同时必须记录下倒数第n个结点的前一个结点。
最后还需要特殊处理如果待删除的结点是头结点的情况,即当 pre 指针是null 时,说明待删除的结点是头结点,直接返回 head.next
1 class Solution {
2 public ListNode removeNthFromEnd(ListNode head, int n) {
3
4 ListNode fast = head, slow = head;
5 for(int i = 0; i < n; i++){
6 if(fast == null){
7 return null;
8 }
9 fast = fast.next;
10 }
11 ListNode pre = null;
12 while(fast != null){
13 fast = fast.next;
14 pre = slow; // 记录slow前一个结点
15 slow = slow.next;
16 }
17 if(pre == null){ // 如果pre为空,说明待删除的是第一个结点
18 return head.next;
19 }
20 pre.next = slow.next; // 否则删除slow这个结点
21 return head;
22 }
23 }
leetcode 执行用时:0 ms > 100.00%, 内存消耗:36.8 MB > 56.55%
复杂度分析:
时间复杂度:O(n)。快指针完整的遍历了一次链表,所以时间复杂度为O(n)。
空间复杂度:O(1)。
思路二:借助辅助头结点
借助辅助头结点来代替删除的是第一个结点时的特别处理,因为需要删除倒数第 n 个结点,所以需要记录倒数第 n 个结点的前一个结点,即倒数第 n + 1个结点。所以让快指针先走 n + 1步,这样当快指针为空时,慢指针所指结点就是倒数第 n + 1个结点。
前面之所以slow指针不记录倒数第 n 个结点,而是记录倒数第 n 结点,是因为担心链表长度刚好是 n, slow指针不好赋初值,如果初值为 null, 那就没办法和 fast 一起向下遍历,如果初值为 head, 但是又担心链表刚好为(n + 1)的情况,这时如果使用 slow == head来判断待删除的结点是第一个结点就有问题。
1 class Solution {
2 public ListNode removeNthFromEnd(ListNode head, int n) {
3 ListNode dummyHead = new ListNode();
4 dummyHead.next = head;
5 ListNode fast = dummyHead, slow = dummyHead;
6 for(int i = 0; i < n + 1; i++){ // 快指针先走 n + 1步
7 if(fast == null){
8 return null;
9 }
10 fast = fast.next;
11 }
12 while(fast != null){
13 fast = fast.next;
14 slow = slow.next;
15 }
16 slow.next = slow.next.next; // 否则删除slow这个结点
17 return dummyHead.next;
18 }
19 }
leetcode 执行用时:0 ms > 100.00%, 内存消耗:36.1 MB > 91.46%, 空间消耗略小于思路一
复杂度分析:
时间复杂度:O(n)。快指针完整的遍历了一次链表,所以时间复杂度为O(n)。
空间复杂度:O(1)。