Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

leetcode19:删除链表倒数第 n 个结点 #16

Open
sisterAn opened this issue Apr 13, 2020 · 13 comments
Open

leetcode19:删除链表倒数第 n 个结点 #16

sisterAn opened this issue Apr 13, 2020 · 13 comments
Labels

Comments

@sisterAn
Copy link
Owner

sisterAn commented Apr 13, 2020

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

示例:

给定一个链表: 1->2->3->4->5,  n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.

说明:

给定的 n 保证是有效的。

进阶:

你能尝试使用一趟扫描实现吗?

附leetcode地址:leetcode

@sisterAn sisterAn changed the title leetcode876:求链表的中间结点 leetcode19:删除链表倒数第 n 个结点 Apr 13, 2020
@taoeer
Copy link

taoeer commented Apr 14, 2020

快慢指针法
快指针先走n个节点,然后快慢指针一起,知道快指针走到null,这时慢指针指向n-1,将慢指针指向快指针。

@sisterAn
Copy link
Owner Author

sisterAn commented Apr 14, 2020

解法:快慢指针

解题思路: 需要删除链表中的倒数第 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 节点

const 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 节点

const 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)

leetcode

@plane-hjh
Copy link

瓶子君,不是很明白这里

slow.next = slow.next.next
return head

改变的是 slow,为什么返回的是 head

@gyt95
Copy link

gyt95 commented Jul 7, 2020

瓶子君,不是很明白这里

slow.next = slow.next.next
return head

改变的是 slow,为什么返回的是 head

因为题目要求在删除了指定节点后,需要返回的是链表的头结点。所以返回的是head

@hundren
Copy link

hundren commented Aug 7, 2020

new ListNode(0) 这个是什么

@qianlongo
Copy link

var removeNthFromEnd = function(head, n) {
    const dump = new ListNode(-1)

    dump.next = head

    let fast = dump
    let slow = dump

    while (n--) {
        fast = fast.next
    }

    while (fast.next) {
        fast = fast.next
        slow = slow.next
    }

    slow.next = slow.next.next

    return dump.next
};

@zsg857981535
Copy link

zsg857981535 commented Sep 14, 2020

注释写的先走的步数是不是有问题?

第一种解法: n-- 先走 n 步,
第二种解法: --n 先走n-1步

@AnranS
Copy link

AnranS commented Nov 17, 2020

var removeNthFromEnd = function (head, n) {
    // 哨兵节点
    let dump = new ListNode();
    dump.next = head;
    // 快慢指针
    let fast = slow =  dump;

    // 快指针先走n步
    for (let i = 0; i < n; i++) {
        fast = fast.next;
    }
    // 快指针走到最后,当前slow为倒数第n+1个节点
    while(fast && fast.next) {
        fast = fast.next;
        slow = slow.next;
    }
    slow.next = slow.next.next;
    return dump.next;
};

@Anoi1018
Copy link

借用数组存放链表节点 通过数组下标进行删除
var removeNthFromEnd = function(head, n) {
if(head === null) return null;
var arr = [];
var p = head;
while(p){
arr.push(p);
p = p.next;
}
var pos = arr.length-n;
if(pos===0) return head.next;
arr[pos-1].next = arr[pos].next;

for(let i=pos+1;i<=arr.length-1;i++){
    arr[i-1]=arr[i];
}

return head;

};

@Anoi1018
Copy link

快慢指针:
var removeNthFromEnd = function(head, n) {
if(head === null) return head;
var slow = head,fast = head,count=n+1;//找到倒数第n+1个节点
//通过删除倒数第n+1个节点的后继节点来实现删除倒数第n个节点

//快指针先走n+1步 再快慢指针同步走 当快指针指向null 慢指针指向倒数第n+1个节点
while(fast && count>0){
    fast = fast.next;
    count--;
}
//当倒数第n个节点为head 快指针走到第n步即为null
if(fast===null && count!==0){
    head = head.next;
    return head;
}
while(fast){
    fast = fast.next;
    slow = slow.next;
}

slow.next = slow.next.next;
return head;

};

@azl397985856
Copy link

题目地址(19. 删除链表的倒数第 N 个节点)

https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/

题目描述

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

示例:

给定一个链表: 1->2->3->4->5, 和 n = 2.

当删除了倒数第二个节点后,链表变为 1->2->3->5.
说明:

给定的 n 保证是有效的。

进阶:

你能尝试使用一趟扫描实现吗?

前置知识

  • 链表
  • 双指针

公司

  • 阿里
  • 百度
  • 腾讯
  • 字节

思路

这里我们可以使用双指针算法,不妨设为指针 A 和 指针 B。指针 A 先移动 n 次, 指针 B 再开始移动。当 A 到达 null 的时候, 指针 B 的位置正好是倒数第 n。这个时候将 B 的指针指向 B 的下下个指针即可完成删除工作。

算法:

  • 设置虚拟节点 dummyHead 指向 head(简化判断,使得头结点不需要特殊判断)

  • 设定双指针 p 和 q,初始都指向虚拟节点 dummyHead

  • 移动 q,直到 p 与 q 之间相隔的元素个数为 n

  • 同时移动 p 与 q,直到 q 指向的为 NULL

  • 将 p 的下一个节点指向下下个节点

19.removeNthNodeFromEndOfList

(图片来自: https://github.com/MisterBooo/LeetCodeAnimation)

关键点解析

  1. 链表这种数据结构的特点和使用

  2. 使用双指针

  3. 使用一个 dummyHead 简化操作

代码

代码支持: JS, Java,CPP

Javascript Code:

/**
 * @param {ListNode} head
 * @param {number} n
 * @return {ListNode}
 */
var removeNthFromEnd = function (head, n) {
  let i = -1;
  const noop = {
    next: null,
  };

  const dummyHead = new ListNode(); // 增加一个dummyHead 简化操作
  dummyHead.next = head;

  let currentP1 = dummyHead;
  let currentP2 = dummyHead;

  while (currentP1) {
    if (i === n) {
      currentP2 = currentP2.next;
    }

    if (i !== n) {
      i++;
    }

    currentP1 = currentP1.next;
  }

  currentP2.next = ((currentP2 || noop).next || noop).next;

  return dummyHead.next;
};

Java Code:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        TreeNode dummy = new TreeNode(0);
        dummy.next = head;
        TreeNode first = dummy;
        TreeNode second = dummy;

        if (int i=0; i<=n; i++) {
            first = first.next;
        }

        while (first != null) {
            first = first.next;
            second = second.next;
        }

        second.next = second.next.next;

        return dummy.next;
    }
}

CPP Code:

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode *p = head, *q = head;
        while (n--) q = q->next;
        if (!q) {
            head = head->next;
            delete p;
            return head;
        }
        while (q->next) p = p->next, q = q->next;
        q = p->next;
        p->next = q->next;
        delete q;
        return head;
    }
};

复杂度分析

  • 时间复杂度:$O(N)$
  • 空间复杂度:$O(1)$

大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。更多算法套路可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 37K star 啦。
大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。

@JohnApache
Copy link

我自己用了递归回溯的方式,也是只扫描了一遍,虽然没有 快慢指针 巧妙,但大家也可以参考一下

const removeNthFromEnd = (head: Node | undefined, n: number) => {
    if (n <= 0) return head;
    if (!head) return head;
    let result: Node | undefined = head;
    const fn = (prev: Node | undefined, node: Node | undefined): number => {
        if (!node) return 0;
        const deep = fn(node, node.next) + 1;
        if (deep === n) {
            if (!prev) {
                // 删除的head
                result = node.next;
            } else {
                const nextNode = node.next;
                prev.next = nextNode;
            }
        }
        return deep;
    };
    fn(undefined, head);
    return result;
};

@jxtprivate
Copy link

注释写的先走的步数是不是有问题?

第一种解法: n-- 先走 n 步, 第二种解法: --n 先走n-1步

你理解为slow和fast闭区间内的步数就可以了

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests