[路飞]_每天刷leetcode_50(删除链表的倒数第N个结点 Remove Nth Node from end of list)

122 阅读2分钟

「这是我参与2022首次更文挑战的第9天,活动详情查看:2022首次更文挑战

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

LeetCode传送门19. 删除链表的倒数第 N 个结点

原题

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

Given the head of a linked list, remove the nthn^{th} node from the end of the list and return its head.

Pasted image 20220126175046.png Example:

Input: head = [1,2,3,4,5], n = 2
Output: [1,2,3,5]

![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5784743d19f745d1a9551ea6486161af~tplv-k3u1fbpfcp-watermark.image?)
Input: head = [1], n = 1
Output: []

Input: head = [1,2], n = 1
Output: [1]

Constraints:

  • The number of nodes in the list is sz.
  • 1 <= sz <= 30
  • 0 <= Node.val <= 100
  • 1 <= n <= sz

思考线


解题思路

看到这道题,我们首先思考怎么样找到倒数第n个结点呢?

在这里我们假设链表的总长度为 L

若我们先设一个快指针fast走到n个结点的地方,剩下的长度就是L-n

若我们此时在head处再设一个慢指针slow,然后让快慢指针一起走,那么快指针走向null的时候,说明慢指针正好在倒数第n个点,而这正是我们想删除的节点。 知道了要删除的点,想找到删除点的前一个是非常容易的,我们只需要让删除节点的前一个指向需要删除节点的下一个即可。

现在删除节点的问题解决了,那么还有一个问题。做完删除操作后我们要返回新链表的头结点。那么我们该怎么办呢?

这里面分为两种情况

  1. 头结点被删除了,我们返回第二个节点。
  2. 头结点没有被删除,我们返回原头结点即可。

我们当然可以算出 list 的长度,然后根据 n和长度的关系判断是否是头结点。不过这么做肯定不优雅。

于是乎我想到了我们的老朋友dummyHead.若设置了dummyHead,我们的头部肯定就是dummyHeadnext节点啦。

若没看之前文章的朋友,我解释一下,dummyHead就是我们人为给链表添加的一个虚拟头结点,这样在某些情况下比较方便计算和去除特殊情况

那么接下来我们写代码就顺水推舟了。


/**
 * Definition for singly-linked list.
 * class ListNode {
 *     val: number
 *     next: ListNode | null
 *     constructor(val?: number, next?: ListNode | null) {
 *         this.val = (val===undefined ? 0 : val)
 *         this.next = (next===undefined ? null : next)
 *     }
 * }
 */

function removeNthFromEnd(head: ListNode | null, n: number): ListNode | null {

    const dummyHead = new ListNode(0);
    dummyHead.next = head; // 为了不考虑删除头结点的情况,我们增加一个虚拟头结点
    let fast = dummyHead;
    let slow = dummyHead;
    while(n--) { // 走n步,到达dummyHead后的第n+1个结点,也就是head后的第n个结点
        fast = fast.next;
    }
    while(fast.next) { // 同时走,若fast.next === null的时候,证明slow走到了 倒数第n+1个结点。
        slow = slow.next;
        fast = fast.next;
    }
    slow.next = slow.next.next; // 删除倒数第n个结点
    return dummyHead.next; // 返回头结点
};

时间复杂度

O(n): n为链表的长度,因为我们的fast指针要从头走到尾,所以时间复杂度为n。

用经典算法结构来解题

除了上面的方法来获取倒数第n个链表结点外,我们还能怎么办呢?先进后出的办法最容易找到倒数第n个结点了。于是乎我们就瞬间明白了,使用stack来保存结点,然后再执行出栈操作n次即可得到倒数第n个结点的前一个节点,而我们也可以很容易根据栈内元素是否为n来确定删除的是不是原始头结点。

我们可以这样实现上面的想法

function removeNthFromEnd(head: ListNode | null, n: number): ListNode | null {
    const stack = [];
    let point = head;
    while(point) {
        stack.push(point);
        point = point.next;
    }
    const isHeadChange = stack.length === n;
    if(stack.length === n) {
        return head.next;
    }
    while(n--) {
        stack.pop()
    }
    const pre = stack.pop();
    pre.next = pre.next.next;
    return head;
};

时间复杂度

O(n): 因为我们的栈中保存了链表中所有的节点,所有时间复杂度为n.

这就是我对本题的解法,如果有疑问或者更好的解答方式,欢迎留言互动。