29.删除链表的倒数第N个结点

66 阅读2分钟

题目链接

给你一个链表,删除链表的倒数第 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)