「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战」。
每日刷题第23天 2021.1.18
删除链表的倒数第N个结点
- leetcode原题链接:leetcode-cn.com/problems/re…
- 难度:中等
- 方法:双指针、栈
题目
- 给你一个链表,删除链表的倒数第
n个结点,并且返回链表的头结点。
示例
- 示例1
输入: head = [1,2,3,4,5], n = 2
输出: [1,2,3,5]
- 示例2
输入: head = [1], n = 1
输出: []
- 示例3
输入: head = [1,2], n = 1
输出: [1]
提示
- 链表中结点的数目为
sz 1 <= sz <= 300 <= Node.val <= 1001 <= n <= sz进阶: 你能尝试使用一趟扫描实现吗?
解法
解法1: 常规做法
- 第一步:循环遍历一遍链表,记录下链表的总长度
len - 第二步:用总长度
len减去n得到从前往后需要遍历的个数mm为0:当前链表从前往后找的节点数为0,即:需要删除头节点m不为0:遍历m个节点,找到需要删除的节点的前一个节点
- 第三步:链表删除节点的操作,将当前节点的前一节点的
next指向当前节点的后一个节点- 即:
prev.next = prev.next.next
- 即:
var removeNthFromEnd = function(head, n) {
let h = head;
// 记录链表的长度
let len = 0;
while (h) {
len++;
h = h.next;
}
// console.log('链表长度',len);
len = len - n; // 1 1
// 删除头节点 len2 n2
if (len == 0){
head = head.next;
return head;
}
// 删除尾节点
// 删除中间节点
h = head;
while (len > 1 && h) {
// 遍历到n-1节点
len--;
h = h.next;
}
h.next = h.next.next;
return head;
};
解法2: 自己写的双指针做法
- 第一步:
slow指针指向链表的头节点,fast指针和slow开始时都指向链表的头节点。 - 第二步:
while循环fast指针为真的时候执行(第三种解法有优化🆕)fast指针向前遍历n个节点后,判断fast指针是否为空(不是有效的节点),如果为空,则表示需要删除的节点就是slow节点,即头节点,执行slow = slow.next输出即可fast.next为空表示,fast指针已经找到链表的最后一个节点(有效节点),那么slow指针指向的就是需要删除的节点的前一个节点。此时跳出循环breakfast && fast.next均为真时,表示当前还没有找到要删除的节点,因此将slow指针向前移动一个节点,fast指针指向slow指针所指的节点,重新往前遍历n个节点,重复循环♻️,指到找到需要删除的节点为止。
- 第三步:因为只删除一个节点,因此直接将需要删除的节点的前一个节点的
next指向,需要删除的后一个节点即可。
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @param {number} n
* @return {ListNode}
*/
var removeNthFromEnd = function(head, n) {
let slow = head;
let fast = slow;
// 记录需要循环的次数
let nn = n;
while (fast) {
// 一遍循环遍历
while (nn > 0) {
nn--;
fast = fast.next;
}
// fast.next 空 找到尾节点
if (!fast) {
// 不为有效节点
slow = slow.next;
return slow;
}
if (!fast.next) break;
slow = slow.next;
fast = slow;
nn = n;
}
// 连接
// 只删除一个
slow.next = slow.next.next;
return head;
};
解法3: (优化)参考leetcode官方的解题文章后,写的双指针做法
- 第一步:创建新的表头,让
slow指针指向新创建的表头 - 第二步:将
fast指针指向真正的表头head,并往后遍历n个节点,此时slow和fast指针之间的距离,刚好是n - 第三步:(⚠️也是自己写双指针的时候没有考虑到的做法,做了很多重复的遍历)这里不需要将
slow指针赋值给fast指针,重新遍历n个节点;而是在前面第二步的时候已经确定好slow与fast指针之间的距离的基础上,移动slow往后一个节点,同时也将fast往后移动一个节点,始终确保两指针之间的间距为n个节点即可。 - 第四步:当
fast指针指向null,slow刚好指向需要删除的前一个节点,此时将slow.next = slow.next.next - 🏷️总结:这种方法,就不需要单独判断是否删除的是头节点
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @param {number} n
* @return {ListNode}
*/
var removeNthFromEnd = function(head, n) {
// 还可以优化
// 1. 第一次fast移动n后,slow和fast之间的距离就固定了
// 之后slow移动1,fast移动1即可
// 2. 创建新的表头使用
// 创建新的头指针
let dummy = new ListNode(0,head);
let slow = dummy;
let fast = head;
for (let i = 0; i < n; i++) {
// 现将fast指针前移n步
fast = fast.next;
}
// 双指针依次往前变换
while (fast) {
slow = slow.next;
fast = fast.next;
}
slow.next = slow.next.next;
// 返回真正的链表头节点
return dummy.next;
};
附录
- 链表的注意事项⚠️
- 在调用 next 字段之前,始终检查节点是否为空。
- 获取空节点的下一个节点将导致空指针错误。例如,在我们运行 fast = fast.next.next 之前,需要检查 fast 和 fast.next 不为空。
- 仔细定义循环的结束条件。