「前端刷题」19. 删除链表的倒数第 N 个结点

795 阅读3分钟

这是我参与8月更文挑战的第19天,活动详情查看:8月更文挑战

题目

给你一个链表,删除链表的倒数第 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 <= 30
  • 0 <= Node.val <= 100
  • 1 <= n <= sz

解题思路

思路1

双指针的经典应用,如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了。

思路是这样的,但要注意一些细节。

分为如下几步:

  • 首先这里我推荐大家使用虚拟头结点,这样方面处理删除实际头结点的逻辑
  • 定义fast指针和slow指针,初始值为虚拟头结点,如图:

19.删除链表的倒数第N个节点.png

  • fast首先走n + 1步 ,为什么是n+1呢,因为只有这样同时移动的时候slow才能指向删除节点的上一个节点(方便做删除操作),如图:
    19.删除链表的倒数第N个节点1.png

  • fast和slow同时移动,之道fast指向末尾,如题:
    19.删除链表的倒数第N个节点2.png

  • 删除slow指向的下一个节点,如图:
    19.删除链表的倒数第N个节点3.png

代码

/**
 * @param {ListNode} head
 * @param {number} n
 * @return {ListNode}
 */
var removeNthFromEnd = function(head, n) {
    let ret = new ListNode(0, head),
        slow = fast = ret;
    while(n--) fast = fast.next;
    if(!fast) return ret.next;
    while (fast.next) {
        fast = fast.next; 
        slow = slow.next
    };
    slow.next = slow.next.next;
    return ret.next;
};

思路2:快慢指针

需要删除链表中的倒数第 n 个节点,我们需要知道的就是倒数第 n+1 个节点,然后删除删除倒数第 n+1 节点的后继节点即可

步骤:

使用 2 个指针:

  • fast 快指针提前走 n+1
  • slow 指针指向当前距离 fast 倒数第 n 个节点, 初始为 head

然后, fastslow 同步向前走,直到 fast.nextnull

此时,fast 为最后一个节点,slow 就是倒数第 n+1 个节点,此时问题就变更为删除链表中的 slow 的后继节点

但存在一个问题,当链表长度为 n 时,fast 是前进不到 n+1 个节点位置的,所以此时有两种解决思路:

  • 创建一个头节点 preHead ,设置 preHead.next = head ,这样就可以解决以上问题,删除倒数第 n 个节点后,返回的 preHead.next 即可
  • 另外一种是,fast 快指针提前走 n 步后,判断 fast.next 是否为 null ,即 fast 是否是最后一个节点,如果是,则 head 为倒数第 n 个节点,此时问题可以简化为删除头节点;如果不是, fast = fast.nextfast 再前进一步,slow 为倒数第 n+1 个节点,也解决了以上问题。

解决方案一:添加 preHead 节点

var removeNthFromEnd = function(head, n) {
    let preHead = new ListNode(0)
    preHead.next = head
    let fast = preHead, slow = preHead
    // 快先走 n+1 步
    while(n--) {
        fast = fast.next
    }
    // fast、slow 一起前进
    while(fast && fast.next) {
        fast = fast.next
        slow = slow.next
    }
    slow.next = slow.next.next
    return preHead.next
};

解决方案二:单独处理倒数第 n 节点

var removeNthFromEnd = function(head, n) {
    let fast = head, slow = head
    // 快先走 n 步
    while(--n) {
        fast = fast.next
    }
    if(!fast.next) return head.next
    fast = fast.next
    // fast、slow 一起前进
    while(fast && fast.next) {
        fast = fast.next
        slow = slow.next
    }
    slow.next = slow.next.next
    return head
};

时间复杂度:O(n)

空间复杂度:O(1)

最后

曾梦想仗剑走天涯

看一看世界的繁华

年少的心总有些轻狂

终究不过是个普通人

无怨无悔我走我路

「前端刷题」No.19