关于有关找寻链表结点位置问题的分析

这篇具有很好参考价值的文章主要介绍了关于有关找寻链表结点位置问题的分析。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1.删除链表的倒数第n个节点

leetcode题目链接

关于有关找寻链表结点位置问题的分析,链表,leetcode,c++

关于删除链表中倒数第n个节点的问题,一种有效的方法是使用双指针技术,具体地说,是使用两个指针:左指针(L)和右指针(R)。通过这种方法,我们可以充分利用给定的位置信息(即n),以高效地找到并删除目标节点。以下是该方法的详细步骤:

  1. 初始化指针:首先,我们将左指针L和右指针R都初始化为指向链表的头节点。这是因为我们需要从链表的开始处出发来查找目标节点。

  2. 移动右指针:由于我们的目标是删除倒数第n个节点,我们需要找到这个节点的前驱节点。为了达到这个目的,我们让右指针R先向前移动n+1步。这样做的原因是,当右指针R最终指向链表的末尾(NULL)时,左指针L将会指向倒数第n+1个节点,即目标节点的前驱节点。如果链表的长度正好是n,这意味着要删除的是头节点,此时右指针R将会直接指向链表的末尾(NULL),而左指针L仍指向头节点。

  3. 同时移动两个指针:在右指针R已经提前移动了n+1步之后,我们开始同时移动左指针L和右指针R,两者都每次向前移动一步。这一过程持续进行,直到右指针R指向链表的末尾(NULL)。此时,左指针L将正好位于要删除节点的前驱节点。

  4. 删除节点:找到目标节点的前驱节点后,我们接下来需要修改这个前驱节点的next指针,使其指向要删除节点的下一个节点。具体来说,如果左指针L指向的节点是要删除节点的前驱节点,我们就将L->next指向L->next->next,从而实现删除操作。

  5. 特殊情况处理:如果要删除的节点是链表的头节点(即n等于链表的总长度),那么在上述步骤中,我们需要额外处理这种情况。在这种情况下,我们直接将链表的头节点更新为头节点的下一个节点即可。

通过以上步骤,我们可以有效地在单次遍历中删除链表中的倒数第n个节点,而无需首先计算链表的总长度。这种双指针方法不仅提高了效率,而且还简化了操作过程。

简单图解如下所示:

假设有一个具有五个结点的链表,我们要找到并删除倒数第二个结点。为了实现这一目标,我们可以采用双指针方法,具体步骤如下:

  1. 初始化双指针:首先,我们定义两个指针,左指针(L)和右指针(R),并将它们都初始化为指向链表的头节点。这是因为我们需要从链表的起始位置开始寻找目标节点。关于有关找寻链表结点位置问题的分析,链表,leetcode,c++

  2. 移动右指针:由于目标是删除倒数第二个结点,根据双指针方法,我们需要让右指针R先行移动n+1步,其中n为2(因为是倒数第二个)。因此,我们让右指针R向前移动3步。这样做的目的是要使得当右指针R到达链表末尾(NULL)时,左指针L能够正好指向目标节点的前驱节点。关于有关找寻链表结点位置问题的分析,链表,leetcode,c++

  3. 同时移动两个指针:在右指针R已经提前移动了3步之后,我们开始同时移动左指针L和右指针R,每次都向前移动一步。这一过程持续进行,直到右指针R指向链表的末尾(NULL)。此时左指针L将会指向倒数第三个节点,即倒数第二个节点的前驱节点。关于有关找寻链表结点位置问题的分析,链表,leetcode,c++

  4. 删除目标节点:找到倒数第二个节点的前驱节点后,我们需要进行删除操作。具体来说,我们将左指针L所指向的节点的next指针指向其next节点的next节点。即如果左指针L指向的节点是倒数第三个节点,我们就将L->next指向L->next->next,从而跳过了倒数第二个节点,实现了其删除。关于有关找寻链表结点位置问题的分析,链表,leetcode,c++

 按照以上思路建立起来的模型, C++实现代码如下:

class Solution {
public:
     ListNode* removeNthFromEnd(ListNode* head, int n) {
    struct ListNode* L = head;
    struct ListNode* R = head;
    
    for (int i = 0; i < n+1; ++i) 
    {
        R = R->next;
    }
    
    while (R) 
    {
        R = R->next;
        L = L->next;
    }
    struct ListNode* dummy=L->next;
    L->next = L->next->next;
    delete(dummy);
    return head;
    }
};

提交上述代码到LeetCode进行分析后遇到了内存报错,这通常意味着代码中存在内存管理相关的问题。 

关于有关找寻链表结点位置问题的分析,链表,leetcode,c++

经通过报错的数据进行排查,遇到的错误是因为在尝试删除从链表末尾数第n个节点时,代码没有正确处理一些特殊情况。具体地说,当R指针在循环中移动n+1次后,如果输入的n等于链表的长度,R将会是nullptr。这种情况下,代码将无法进入while循环,因为R已经是nullptr了。随后,当尝试通过L->next = L->next->next;删除节点时,由于L仍然指向头节点(因为while循环未执行),这将导致错误地尝试删除头节点的前一个节点,而这是不存在的。

此外,代码中还缺少处理当要删除的节点恰好是头节点时的逻辑。为了修复这个问题并处理所有特殊情况,我们可以引入一个哑节点(dummy node)作为新的头节点,这样即使需要删除原始链表的头节点,我们也有一个稳定的节点指针引用来操作链表。

对LeetCode错误案例进行分析,引入哑节点(dummy node)图解如下:

关于有关找寻链表结点位置问题的分析,链表,leetcode,c++

当n与链表长度相同时,R指针移动n+1个结点长度

关于有关找寻链表结点位置问题的分析,链表,leetcode,c++

由于新头结点(哑节点)的引入,原来的链表从长度(n)变为了(n+1),此时如果输入的(n)等于原链表长度,首先移动的指针(我们称之为R)将会指向最后一个节点的下一个位置,即nullptr,而不是最后一个节点。这种设计可以有效地避免输入的(n)等于链表长度时导致的问题,因为哑节点确保了即使需要删除的是头节点,也能够通过L指针(它最终会指向哑节点)来正确地执行删除操作。在这种情况下,L指针最终将指向哑节点,而R指针在经过(n+1)次移动(包括从哑节点到原始头节点的一次移动和随后沿链表向前移动的(n)次移动)后会指向nullptr。这时,L->next实际上指向原链表的头节点,即我们需要删除的节点。通过更新L->next来跳过这个节点,我们就能够实现从链表末尾数第(n)个节点的删除。这样,无论是需要删除头节点还是链表中的其他节点,我们都有一个统一且稳定的处理流程。

简而言之,引入哑节点并使得链表长度视觉上增加1,使得算法能够统一处理包括删除头节点在内的所有情况,从而避免了当删除节点正好是头节点时可能出现的边界问题。

修正后的代码如下: 

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummy = new ListNode(0);
        dummy->next = head;
        ListNode* L = dummy;
        ListNode* R = dummy;
        for (int i = 0; i < n + 1; ++i) {
            R = R->next;
        }

        while (R != nullptr) {
            R = R->next;
            L = L->next;
        }

        ListNode* toDelete = L->next;
        L->next = L->next->next;
        delete toDelete;

        ListNode* newHead = dummy->next;
        delete dummy;
        return newHead;
    }
};

提交到 LeetCode 结果如下:

关于有关找寻链表结点位置问题的分析,链表,leetcode,c++

2.链表的中间结点

leetcode题目链接

关于有关找寻链表结点位置问题的分析,链表,leetcode,c++

对于寻找链表中间节点这类问题,由于没有一个明确且固定的节点位置标识中间,且该位置随着链表长度的变化而变化,我们通常采用双指针技术,其中一个指针称为快指针(记为R),另一个称为慢指针(记为L)。为了保证两者的相对位置,我们让快指针R每次移动两步,而慢指针L每次移动一步。这样,当链表长度为奇数时,快指针R走到链表尾部(即R->next == nullptr)时,慢指针L就恰好指向链表的中间节点。

在实际应用中,我们还需要考虑链表长度为偶数的情况。对于长度为偶数的链表,当快指针R的下一个节点是nullptr(即R->next == nullptr)时,慢指针L将位于中间两个节点的前一个。根据不同的需求,有时可能需要返回中间两个节点的后一个,此时可以在循环结束后将L向后移动一位。

进一步分析,为了确保快指针R可以安全地走两步,我们需要对链表长度为1和2的特殊情况进行处理。当链表只有一个节点时(即R->next == nullptr立即成立),我们直接返回L,因为此时链表中仅有的一个节点即为中间节点。

关于有关找寻链表结点位置问题的分析,链表,leetcode,c++

当链表有两个节点时,如果题目要求在有两个中间节点时返回第二个节点,那么初始时快指针R先向后走一步,此时如果R->next == nullptr,则按照题目要求返回L->next。 

关于有关找寻链表结点位置问题的分析,链表,leetcode,c++ 

考虑一个长度为3以及长度为4的链表作为例子。按照快慢指针的移动规则,初始时两者都位于头节点。快指针R首先向前移动两步,若移动一步后R->next=nullptr此时返回L,若移动两步后R->next=nullptr此时返回L->next,以上两种情况与其长度为1以及长度为2的链表返回情况相同。若链表长度为3,当快指针移动一步后,因为R->next不是nullptr,所以快指针继续移动,但在下一步R->next变为nullptr,此时根据规则返回L->next,即中间节点。对于长度为4的链表,快指针首次移动两步后,R->next不是nullptr,可以继续移动;快指针再次移动两步后,R变为nullptr,这时根据规则返回L->next,即第二个中间节点。以上两种情况与其长度为1以及长度为2的链表返回情况相同,为此我们使用while循环来解决此类问题。

关于有关找寻链表结点位置问题的分析,链表,leetcode,c++

关于有关找寻链表结点位置问题的分析,链表,leetcode,c++

通过上述分析,我们实际上将整个链表切分为由两个节点组成的小段(对于链表总长度为奇数的情况,最后会剩下一个单独的节点作为一个段),快指针R在每个小段的末尾检查是否可以继续前进,而慢指针L则稳步前进,最终准确地定位到链表的中间位置。

按照以上思路建立起来的模型, C++实现代码如下:

class Solution {
public:
    ListNode* middleNode(ListNode* head) {
        ListNode* L=head;
        ListNode* R=head;
        while(R){
            if(R->next==nullptr)return L;
            R=R->next;
            if(R->next==nullptr)return L->next;
            R=R->next;
            L=L->next;
        }
          return L;
    }
};

 提交到 LeetCode 结果如下:关于有关找寻链表结点位置问题的分析,链表,leetcode,c++

3.对于以上两类问题的总结

在解决链表中的两个不同问题——删除链表中倒数第n个节点和寻找链表中间节点时,我们都采用了双指针技术,但是具体的应用方式和目的存在一定的差异。以下是对这两种方法的综合分析,包括它们的相同点与不同点。

相同点

  1. 双指针的使用:两种方法都利用了双指针(一个快指针和一个慢指针)来高效地遍历链表,减少了时间复杂度。

  2. 单次遍历:通过合理设置指针的移动规则,两种方法都能够在单次遍历中达到目的,避免了多次遍历链表,提高了算法的效率。

  3. 指针的相对移动速度:在两个问题中,其核心主要是利用双指针不同的移动速度,这种不同速度的设置是为了在遍历过程中构建出特定的位置关系,从而实现各自的目标。

  4. 均考虑链表的临界情况:对于以上两个问题的分析以及代码实现,均是先考虑其普遍存在的情况,从而确定整体实现代码的思路。这种方法允许我们首先构建出一个基本的算法框架,确保它能够处理大多数标准情况。而后,我们根据链表的临界情况给出判断条件,这些临界情况包括但不限于链表长度为0(空链表)、1(只有一个节点的链表)、或者当要删除的节点正好是头节点等情况。通过这种方式,我们确保算法既能够处理一般情形,又能够妥善处理边缘或特殊情况。

不同点

  1. 目标不同

    • 删除倒数第n个节点的方法旨在通过双指针找到倒数第n+1个节点,以便删除其后的节点。
    • 寻找中间节点的方法则是利用快慢指针的速度差来当快指针到达链表末尾时,使慢指针正好位于中间节点。
  2. 指针初始化不同

    • 在删除倒数第n个节点时,两个指针都从头节点的前一个结点开始,并且右指针先单独向前移动n+1步,然后两个指针同时移动。
    • 在寻找中间节点时,快慢指针同样从头节点开始,但是它们从一开始就以不同的速度同时移动,直到快指针无法继续前进。
  3. 处理特殊情况的方式不同

    • 删除倒数第n个节点需要考虑特殊情况,如删除的是头节点,这时需要特别处理更新头节点。
    • 寻找中间节点的算法则需要考虑链表长度为奇数或偶数的情况,以及如何根据需求返回正确的中间节点(对于偶数长度的链表,可能需要决定是返回两个中间节点的前一个还是后一个)。
  4. 算法的目的和结果文章来源地址https://www.toymoban.com/news/detail-834762.html

    • 删除倒数第n个节点的方法关注于修改链表结构,通过改变指针引用来删除特定的节点。
    • 寻找中间节点的方法则专注于定位链表的中间位置,不涉及修改链表结构。

到了这里,关于关于有关找寻链表结点位置问题的分析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Leetcode刷题之两两交换链表中的结点和相交链表

    只有把抱怨环境的心情,化为上进的力量,才是成功的保证。       ——罗曼·罗兰 目录 🍉一.相交链表 💐1.双指针 🍍2.计算长度加双指针 🍒二.两两交换链表中的结点  🍌1.迭代  给你两个单链表的头节点  headA  和  headB  ,请你找出并返回两个单链表相交的起始节点

    2024年02月04日
    浏览(35)
  • Leetcode刷题之回文链表和交换链表中的结点

    竭力履行你的义务,你应该就会知道,你到底有多大的价值。      --列夫.托尔斯泰 目录 🪴一.回文链表 🌻1.快慢指针  🍁2.把值存入数组中,然后使用双指针  🌼二.交换链表中的结点  🍂1.快慢指针 给你一个单链表的头节点  head  ,请你判断该链表是否为回文链表。如

    2024年02月04日
    浏览(50)
  • 【LeetCode】19. 删除链表的倒数第 N 个结点

    给你一个链表,删除链表的倒数第 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] 这题直接不会 循环迭代 为什么用 head 代替 dummy 不行? 因为可能存在只有一

    2024年02月07日
    浏览(43)
  • LeetCode | 19. 删除链表的倒数第 N 个结点

    OJ链接 思路: 定义虚拟头节点 dummy 并初始化使其指向 head 然后定义快慢指针 让快指针先走n步 然后一起走 最后删除倒数第n个节点 然后释放虚拟节点 dummy

    2024年02月04日
    浏览(75)
  • LeetCode:19. 删除链表的倒数第 N 个结点

    🍎道阻且长,行则将至。🍓 🌻算法,不如说它是一种思考方式🍀 算法专栏: 👉🏻123 题目描述 :给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。 来源:力扣(LeetCode) 难度: 中等 提示: 链表中结点的数目为 sz 1 = sz = 30 0 = Node.val = 100 1 = n = sz 示例

    2024年02月02日
    浏览(74)
  • LeetCode19:删除链表的倒数第N个结点

    力扣题目链接

    2024年01月21日
    浏览(50)
  • 【代码随想录 | Leetcode | 第六天】链表 | 反转链表 | 两两交换链表中的节点 | 删除链表的倒数第 N 个结点

    欢迎来到小K的Leetcode|代码随想录|专题化专栏,今天将为大家带来反转链表、两两交换链表中的节点和删除链表的倒数第N个节点的分享 ✨ ✨题目链接点这里 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。 示例 1: 示例 2: 示例 3: 提示: 链表中节点的数

    2024年02月16日
    浏览(58)
  • 算法通关村第一关——链表经典问题之寻找两个链表的第一个公共结点

    这是一道经典的链表问题,来自剑指offer52,题目是这样的:输入两个链表,找出它们的第一个公共结点,如下图所示: 两个链表的头结点均已知,相交之后成为一个单链表,但是相交的位置未知,并且相交之前的结点数也是未知的,请设计算法找到两个链表的合并点。 第一

    2024年02月16日
    浏览(58)
  • 算法通关村第一关------链表经典问题之寻找第一个公共子结点

    哈希和集合 栈 拼接两个字符串 同步相消 1、哈希和集合法:         将一个链表存入Map(或者集合)中,然后遍历第二个链表,在遍历的同时,检查在Hash(或者集合)中是否包含此节点。  2、栈方法:         主要思想:栈是后进先出原则,stack.peek()方法每次比较栈顶

    2024年02月12日
    浏览(39)
  • 关于链表的题目—leetcode

    问题描述: 给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。 返回删除后的链表的头节点。 示例 1: 输入: head = [4,5,1,9], val = 5 输出: [4,1,9] 解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 - 1 - 9. 示例 2: 输入

    2023年04月25日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包