给你一个链表,删除链表的倒数第
n个结点,并且返回链表的头结点。
解法1 暴力解法
思路
如果我们先知道链表的长度,那么就可以合理推断出需要被删除链表的前一个节点。
于是可以先遍历一遍整个链表,获取长度。但此时如果链表的长度和 n 相等,那么说明需要删除头节点。
然后再让指针走 len - n - 1步,就可以找到需要被删除链表的前一个节点。直接进行删除即可。
代码
function removeNthFromEnd(head: ListNode | null, n: number): ListNode | null {
let cur = head;
let len = 0;
while (cur) {
len++;
cur = cur.next;
}
if (n === len) {
return head?.next ?? null;
}
let dummy = new ListNode(-1, head);
cur = dummy;
for (let i = 0; i < len - n; i++) {
cur = cur.next;
}
cur.next = cur.next?.next ?? null;
return dummy.next;
};
时空复杂度
时间复杂度:虽然两次遍历,但是渐进是 O(n)
空间复杂度:没有额外空间消耗 O(1)
解法2 快慢指针
思路
暴力解法是提前知道了链表长度,而我们可不可以不用知道链表长度依然删除节点呢。
我们可以想象一下滑动窗口的概念,有左右两个边界。所以这里我们可以沿用双指针的方法。
让快指针先走 n 步,然后慢指针从头和快指针一起出发,当快指针走到链表末尾的时候,此时慢指针指向的就是倒数第 n 个节点的前一个节点,此时操作指针删除即可。
至于为什么需要虚拟头节点,那是因为我们不是需要找到倒数第 n 个节点,而是他的前一个节点去操作指针删除。
代码
function removeNthFromEnd(head: ListNode | null, n: number): ListNode | null {
const dummy = new ListNode(-1, head);
let fast = dummy;
let slow = dummy;
for (let i = 0; i < n + 1; i++) {
fast = fast.next ?? null;
}
while (fast) {
slow = slow.next;
fast = fast.next;
}
if (slow.next) {
slow.next = slow.next.next;
}
return dummy.next;
};
时空复杂度
时间复杂度:O(n)
空间复杂度:O(1)