多种方法从尾部移除指定位置的链表节点

连绵的春雨把人困在家乡,于是我继续开始刷着算法题,通过 19. Remove 年th Node From End of List复习了一波链表的操作,这道题也是比较典型的链表问题,值得分享一下。

题目如下所示:

less 复制代码
Given the head of a linked list, remove the nth node from the end of the list and return its head.

 

Example 1:


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

Input: head = [1], n = 1
Output: []
Example 3:

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

简单来说,就是给一个单链表,然后给一个数字n,让我们把尾部第n个节点删除掉,然后返回这个链表的头部。具体例子如上面描述所说,这个题目写得还是比较清晰的。

如果说是给定数字n,是从链表头部开始算位置,那么这道题就非常简单,直接循环遍历到第n个节点,然后把第n个节点之前的节点的next改为第n+1个节点就行了。 但现在却是从尾部开始数n个节点,所以需要做一些处理。

最直接的思路是,把从尾部的问题变成从头部遍历的问题,从尾部开始往前第n个节点,就是从头部往后第(链表长度 - n)个节点被移除。

于是算法思路如下:

    1. 通过遍历获得链表长度L1。
    1. 计算出从头部开始数是第(L1 - n)个节点。
    1. 用一个指针从head开始变量到第(L - n - 1)个节点,然后将它的next指向它下一个的下一个节点,完成节点移除。
    1. 然后返回原来的链表的head。

用Python实现的代码如下:

python 复制代码
class Solution(object):
    def removeNthFromEnd(self, head, n):
        """
        :type head: ListNode
        :type n: int
        :rtype: ListNode
        """
        if head.next == None:
            return None
        
        current = head
        length_list = 0
        while current != None:
            length_list += 1
            current = current.next

        pos = length_list - n

        # it means that we should remove the first node
        if pos <= 0: return head.next
        
        # define the pointer to start to iterate the nodes
        moving_pointer = head;
        for i in range(pos - 1):
            moving_pointer = moving_pointer.next


        # If the node that we want to remove is not exist, just return the original list
        if moving_pointer.next == None:
            return head
        
        # Skip the deleted node
        moving_pointer.next = moving_pointer.next.next

        return head

这个方案比较符合人性,也容易想到,但是缺点是必须先遍历一遍整个链表获得链表长度,然后再移动指针到删除的那个节点之前的节点。

有没有办法不先获取链表长度,又能顺利从链表头部移动到想要删除的节点之前呢?我想起了古代小兵探路的故事,可以让一个指针先行一步,探出一条准确的步数,然后再让一个指针走L1(链表长度) - n - 1个节点就行了。

我简单绘制了一张图, 步骤如下所示:

  1. 定义一个dump节点,它的next指向head,fast和slow都指向dump。
  2. 用一个fast指针和slow指针分别进行移动,fast指针优遍历前n个节点,那么剩下没遍历的节点数量刚好就是L1 - n。
  3. 然后再继续遍历fast和slow,就能顺利遍历到第L1 - n -1个节点,然后移除掉下一个节点,然后返回修改后的list。

那么想好之后,快速写出代码:

python 复制代码
class Solution(object):
   def removeNthFromEnd(self, head, n):
        """
        :type head: ListNode
        :type n: int
        :rtype: ListNode
        """
        dump = ListNode(0)
        dump.next = head
        fast = dump
        slow = dump

        for i in range(n+1):
            fast = fast.next

        # move the fast to the end of the list
        while fast != None:
            fast = fast.next
            slow = slow.next
        
        if slow.next == None:
            return dump.next
        
        slow.next = slow.next.next

        return dump.next

Js版本类似:

ini 复制代码
/**
 * @param {ListNode} head
 * @param {number} n
 * @return {ListNode}
 */
var removeNthFromEnd = function(head, n) {

    let dump = new ListNode(0);
    dump.next = head;
    let fastPointer = dump;
    let slowPointer = dump;

    for (let i = 0; i <= n; i++) {
        fastPointer = fastPointer.next;
    }

    while(fastPointer != undefined) {
        fastPointer = fastPointer.next;
        slowPointer = slowPointer.next;
    }

    slowPointer.next = slowPointer.next.next;

    return dump.next;
};

如下图所示,这个算法的Runtime非常快,但是memory使用就比较大,毕竟用了2个指针以及dump去实现了遍历。

相关推荐
devlei1 小时前
从源码泄露看AI Agent未来:深度对比Claude Code原生实现与OpenClaw开源方案
android·前端·后端
努力的小郑3 小时前
Canal 不难,难的是用好:从接入到治理
后端·mysql·性能优化
Victor3563 小时前
MongoDB(87)如何使用GridFS?
后端
Victor3563 小时前
MongoDB(88)如何进行数据迁移?
后端
小红的布丁4 小时前
单线程 Redis 的高性能之道
redis·后端
GetcharZp4 小时前
Go 语言只能写后端?这款 2D 游戏引擎刷新你的认知!
后端
宁瑶琴5 小时前
COBOL语言的云计算
开发语言·后端·golang
普通网友6 小时前
阿里云国际版服务器,真的是学生党的性价比之选吗?
后端·python·阿里云·flask·云计算
IT_陈寒6 小时前
Vue的这个响应式问题,坑了我整整两小时
前端·人工智能·后端
Soofjan7 小时前
Go 内存回收-GC 源码1-触发与阶段
后端