数据结构刷题训练——链表篇(三)

news2024/11/26 5:21:39

目录

文章目录

前言

1. 题目一:环形链表Ⅱ

1.1 思路

1.2 分析

1.3 题解

1.4 方法二

2. 题目二:复制带随机指针的链表

2.1 思路

2.2 分析

2.3 题解

总结


前言

        在这个专栏博客中,我们将提供丰富的题目资源和解题思路,帮助读者逐步提高解题能力。同时,我们也将分享一些刷题技巧和经验,帮助读者更加高效地进行刷题训练。通过持之以恒的努力和不断的实践,相信读者可以在数据结构领域取得长足的进步。本期将是数据结构刷题训练链表篇的最后一期,后续我们将进入栈和堆的刷题训练。


1. 题目一:环形链表Ⅱ

题目描述:

示例:

 题目链接:环形链表Ⅱ

1.1 思路

        本题的题意是给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 NULL。

        寻找第一个入环的节点,在上道环形链表的题目中仅仅只是判断是否有环,但没法确定入环的节点,这道题目属于之前题目的进阶。那我们要如何去解决呢?

        这里向大家说明一点,如果在刷题的时候,遇到一个题目,连题意都很难理解或者,是无从下手,那这道题目就不再是简简单单的考察代码能力,其中的分析更为重要,没有分析的过程,仅仅只要代码是无法看懂理解的。

1.2 分析

        初看题目毫无思路,但是别担心,我在分析之后记住这种做题思路、方法就可以,在后续刷题中灵活运用。这道题目我们仍然可以使用快慢指针的方法。

        首先我们需要知道带环链表的几种情况:

一般情况:

 环很大

 

 环很小

         在上期中我们将道使用快慢指针遍历链表,快指针一次走两步,慢指针一次走一步,如果它们相遇那它们一定是带环的链表。那问题来了,fast一次走两步,slow一次走一步,一定可以追上吗? 结论是一定能追上。

        为什么?我们可以分析一下,在两个指针都入环以后,快指针一次走两步,慢指针一次走一步,它们的距离会减一,一直走一直减一,最后直到追上,距离变化过程:

N

N-1

N-2

……

2

1

0

        那如果fast一次走3步,slow一次走两步呢?

        假设它们之间的距离是M,fast和slow都进环以后,它们的距离一次就是减2,那它们的距离变化:

M是偶数          M是奇数

M-2                  M-2

M-4                  M-4 

……                  ……

4                        3

2                        1

0                      -1

         如果M是偶数,就刚好追上相遇,如果M是奇数这就说明它们会错过,一旦错过就要继续走一圈,假设环的周长是C,当C为奇数时,当它们错过后,它们之间的距离是C-1,此时C-1必为偶数,下次一定能追上,但如果C是偶数时,C-1为奇数,然而它们每次距离减小2,这样无论怎么走都不会相遇。

         如果继续增大步数呢?fast一次走4步,slow一次走两步。假设进入环后它们的距离是k,那么它们的距离变化:

k是3的倍数          k%3=2            k%3=1

k-3                        k-3                  k-3

k-6                        k-6                   k-6

……                     ……                  ……

6                             5                     4

3                             2                     1

0                            -1                    -2

错过后距离:         C-1                 C-2

         如果C-1或C-2仍然不是3的倍数,那它们仍然始终无法相遇。有人可能会这样想,那让其中一个停下来,另一个去追不就可以了,但我们无法确定慢指针是否进环了,如果没进环,那快指针走多少圈都不会追上。这里只是移动步数的分析,在带环链表中使用快慢指针,最好使用步数差为1的,下面步入正题:

        依据上述结论,我们看这道题目,这里我们让fast一次走2步,slow一次走1步,当fast入环时:

        假设从起点到入环点距离为L,环周长为C

 慢指针入环时:

 

 相遇时:

         由上可以得出:fast走的距离时slow的二倍,那slow走的距离就是X+L,那fast走的路程就是L+X+n*C(n>=0),因为无法确定L于C的关系,如果C很小,那fast就可能走了很多圈。或许有人会疑惑,那slow为什么不用加n*C,因为slow入环以后,fast在slow走一圈之内一定能追上它

 我们根据这些关系可以得到:

L+X+n*C=2(L+X)

化简一下:

L+X=n*C

n*C-X=L

 得到n*C-X=L这个结论再转化一下就是:(n-1)*C+C-X=L

         到这里就一目了然了,一个指针从相遇点开始出发,一个从起点开始出发,最终它们会在入环点相遇。

1.3 题解

根据上述的分析进行编写代码:

struct ListNode *detectCycle(struct ListNode *head) 
{
    struct ListNode* slow,*fast;
    slow=fast=head;
    while(fast&&fast->next)
    {
        slow=slow->next;
        fast=fast->next->next;
        if(slow==fast)
        {
            struct ListNode* meet=slow;
            while(meet!=head)
            {
                head=head->next;
                meet=meet->next;
            }
            return head;
        }
    }
    return NULL;
}

1.4 方法二

        方法二的思路简单,但代码量增多,我们先让两指针相遇,相遇后将相遇的节点的next置为NULL,也就是将环断掉,这样就转化成了链表相交。

 代码如下:

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) 
{
    struct ListNode* curA=headA,*curB=headB;
    int lenA=1,lenB=1;
    while(curA->next)
    {
        curA=curA->next;
        lenA++;
    }
    while(curB->next)
    {
        curB=curB->next;
        lenB++;
    }
    if(curA!=curB)
    {
        return NULL;
    }
    int t= abs(lenA-lenB);
    struct ListNode* longlist=headA, *shortlist=headB;
    if(lenB>lenA)
    {
        longlist=headB;
        shortlist=headA;
    }
    while(t--)
    {
        longlist=longlist->next;
    }
    while(longlist!=shortlist)
    {
        longlist=longlist->next;
        shortlist=shortlist->next;
    }
    return longlist;
}
struct ListNode *detectCycle(struct ListNode *head) 
{
    struct ListNode* slow,*fast;
    slow=fast=head;
    while(fast&&fast->next)
    {
        slow=slow->next;
        fast=fast->next->next;
        if(slow==fast)
        {
            struct ListNode* meet=slow;
            struct ListNode* newnode=meet->next;
            meet->next=NULL;

            return getIntersectionNode(newnode,head);
           
        }
    }
    return NULL;
}

2. 题目二:复制带随机指针的链表

 题目描述:

 示例:

 题目链接:

复制带随机指针的链表

2.1 思路

        或许大部分人读完题目还是像上道题目一样,读完没有思路,甚至连题意都读不懂。

        复制一个链表不难,但这道题目的难点在于每个节点指针域的随机指针,我们很难控制随机指针的指向,单纯的完全复制行不通,复制下来的节点关系要与原链表相同,这才是本道题目的难点。

怎么才能找到随机指针的指向关系呢?

我们的思路是这样的:

  • 遍历原链表,遍历一个节点复制一个节点,然后将复制的节点插入道原节点的后边。
  • 再次遍历插入后的链表,找复制节点的随机指针的指向关系。
  • 最后将复制的链表与原链表分开

2.2 分析

 根据思路进行分析:

 复制后的链表链接道原链表

 这里要理解如何去找复制节点的random指向的节点。

我们可以这样操作:copy->random=cur->random->next

         重点就是这句的理解,cur指向的是原链表中的节点,复制的也就是cur指向的节点,然后将复制的节点(copy)链接到cur节点后,那copy节点的random不就在cur节点的random指向节点的下一个吗?

        意思就是说,遍历原链表中的节点,找到当前节点的random就可以找到复制节点的random,因为复制节点(copy)的random,就是当前节点(原链表)random指向节点的下一个节点。

         这里要好好理解,例如:13号这个节点(cur),13节点的下一个节点就是复制的13号节点。cur的random指向的是7号节点(原链表),7号节点的下一个节点不就是复制的13号节点(copy)要找的random吗?

        但这里需要特殊处理一种情况,就是原链表random指向为NULL的情况,这里就直接将复制节点的random置为NULL。

2.3 题解

根据上述的分析,开始编写代码:

struct Node* copyRandomList(struct Node* head)
{
	struct Node* cur=head;
    while(cur)                    //复制节点
    {
        struct Node* copynode=(struct Node*)malloc(sizeof(struct Node));
        struct Node* next=cur->next;
        copynode->val=cur->val;
        
        //插入到原节点后
        cur->next=copynode;
        copynode->next=next;
        
        cur=next;
    }
    cur=head;
    
//寻找复制节点的random
    while(cur)        
    {
        struct Node* copynode=cur->next;
        if(cur->random==NULL)        //随机指针为NULL进行特殊处理
        {
            copynode->random=NULL;
        }
        else
        {
            copynode->random=cur->random->next;
        }
        
        cur=cur->next->next;
    }
     cur=head;
     struct Node* copyhead,*copytail;
     copyhead=copytail=NULL;

//还原原节点,将两个链表分开
    while(cur)
    {
        
        struct Node* copynode=cur->next;
        struct Node* next=cur->next->next;
        //将复制节点链接到新的链表,这里使用的是无头节点的链表,所有需要特殊处理
        if(copyhead==NULL)
        {
            copyhead=copytail=copynode;
        }
        else
        {
            copytail->next=copynode;
            copytail=copytail->next;
        }
        //恢复原链表
        cur->next=next;
        cur=next;

    }
    return copyhead;
}


 

总结

        本期的题目相对较为复杂,希望大家能够好好的消化理解,最后,感谢你的阅读和支持。希望你在这个数据结构的学习旅程中能够获得满满的收获和成就感。愿我们共同努力,不断探索和挑战,成为数据结构领域的行家里手!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/848053.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

浅谈智能配电房在海岛中的应用

安科瑞 华楠 摘要:以海陵岛旅游区海岛智能配电房设计方案为研究对象,从功能设计、逻辑设计和拓扑设计三方面进行 分析,得出契合海陵岛实际和海岛特点的智能配电房方案设计。通过对设计方案研究,总结提炼了海岛智 能配电房组成要素,为进一步提炼统一规范的智能配电房…

【深度学习】【风格迁移】Zero-shot Image-to-Image Translation

论文:https://arxiv.org/abs/2302.03027 代码:https://github.com/pix2pixzero/pix2pix-zero/tree/main 文章目录 Abstract1. Introduction相关工作3. Method Abstract 大规模文本到图像生成模型展示了它们合成多样且高质量图像的显著能力。然而&#x…

【网络编程】实现一个简单多线程版本TCP服务器(附源码)

TCP多线程 🌵预备知识🎄 Accept函数🌲字节序转换函数🌳listen函数 🌴代码🌱Log.hpp🌿Makefile☘️TCPClient.cc🍀TCPServer.cc🎍 util.hpp 🌵预备知识 &…

未来C#上位机软件发展趋势

C#上位机软件迎来新的发展机遇。随着工业自动化的快速发展,C#作为一种流行的编程语言在上位机软件领域发挥着重要作用。未来,C#上位机软件可能会朝着以下几个方向发展: 1.智能化:随着人工智能技术的不断演进,C#上位机…

数据结构--最小生成树

数据结构–最小生成树 连通图 \color{red}连通图 连通图的生成树是 包含图中全部顶点的一个极小连通子图 \color{red}包含图中全部顶点的一个极小连通子图 包含图中全部顶点的一个极小连通子图。 若图中顶点数为n,则它的生成树含有 n-1 条边。对生成树而言&#xff…

RFID技术助力半导体制造行业自动化生产

由于芯片短缺问题和近2年海运拥堵和成本上升等因素,致使全球资本对于芯片制造工厂的投入增大,而中兴、华为的例子已经凸显出国产半导体供应链的重要性,除去地缘政治上的意义,发展半导体其实是中国经济的转型的必走之路。 半导体生…

Vue2(生命周期,列表排序,计算属性和监听器)

目录 前言一,生命周期1.1,生命周期函数简介1.2,Vue的初始化流程1.3,Vue的更新流程1.4, Vue的销毁流程1.5, 回顾生命周期1.,6,代码演示1.6-1,beforeCreate1.6-2,created1.6-3&#xf…

雨水旋流过滤器、旋流雨水过滤器、水力旋流雨水过滤器、旋流分离器、旋流沉砂一体机、旋流沉砂井

产品组成 主要材料:PE304不锈钢 组成:过滤器筒体、桶盖、截止阀(选配)、不锈钢滤网 工作原理 雨水由过滤器进水口进入时,水流被引导沿过滤器内壁切线方向进入筒体。在水力、重力等作用下,形成雨水紧贴过…

【基础类】—前端算法类

一、排序 1. 排序方法列表 2. 常见排序方法 快速排序选择排序希尔排序 二、堆栈、队列、链表 堆栈、队列、链表 三、递归 递归 四、波兰式和逆波兰式 理论源码

Unity之ShaderGraph 节点介绍 Utility节点

Utility 逻辑All(所有分量都不为零,返回 true)Any(任何分量不为零,返回 true)And(A 和 B 均为 true)Branch(动态分支)Comparison(两个输入值 A 和…

wm8960没有声音

最近在imx6ull上调试这个声卡,用官方的镜像是能发声的,换到自己做的镜像上,就没有声音。 记录一下过程: 内核和设备树。只要有下面的显示,就说明加载成功。 再看一下aplay的显示 到此,驱动都是正常的。但…

每天一道leetcode:剑指 Offer 32 - II. 从上到下打印二叉树 II(适合初学者)

今日份题目: 从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。 示例 例如: 给定二叉树: [3,9,20,null,null,15,7], 3 / \ 9 20 / \ 15 7 返回其层次遍历结果: [ [3], […

selenium常见等待机制及其特点和使用方法

目录 1、强制等待 2、隐式等待 3、显式等待 1、强制等待 强制等待是在程序中直接调用Thread.sleep(timeout) ,来完成的,该用法的优点是使用起来方便,语法也比较简单,缺点就是需要强制等待固定的时间,可能会造成测试的时间过…

APP外包开发的android开发模式

开发 Android 应用有多种方法,每种方法都有其优势和适用场景。综合考虑各自的特点,你可以根据项目的需求和团队的技能选择最合适的开发方法。今天和大家分享几种常见的开发方法以及它们之间的对比,希望对大家有所帮助。北京木奇移动技术有限公…

探索泛型与数据结构:解锁高效编程之道

文章目录 引言第一部分:了解泛型1.1 为什么使用泛型1.2 使用泛型的好处 第二部分:泛型的使用场景2.1 类的泛型2.2 方法的泛型2.3 接口的泛型 第三部分:泛型通配符3.1 通配符3.2 通配符的受限泛型 第四部分:数据结构和泛型的应用4.…

边缘计算框架 Baetyl v2.4.3 正式发布

导读Baetyl v2.4.3 版本已经发布,对 v2.3.0 版本的部分功能进行了升级优化。公告称,这些新功能继续遵循云原生理念,构建了一个开放、安全、可扩展、可控制的智能边缘计算平台。 Baetyl 项目由百度发起,基于百度天工 AIoT 智能边缘…

【LeetCode】数据结构题解(9)[复制带随机指针的链表]

复制带随机指针的链表 😉 1.题目来源👀2.题目描述🤔3.解题思路🥳4.代码展示 所属专栏:玩转数据结构题型❤️ 🚀 >博主首页:初阳785❤️ 🚀 >代码托管:chuyang785❤…

使用Pytest集成Allure生成漂亮的图形测试报告

目录 前言 依赖包安装 Pytest Allure Pytest Adaptor 改造基于Pytest的测试用例 生成测试报告 运行测试 生成测试报告 打开测试报告 资料获取方法 前言 之前写过一篇生成测试报告的博客,但是其实Allure首先是一个可以独立运行的测试报告生成框架&#xff…

(杭电多校)2023“钉耙编程”中国大学生算法设计超级联赛(6)

1001 Count 当k在区间(1n)/2的左边时,如图,[1,k]和[n-k1,n]完全相同,所以就m^(n-k) 当k在区间(1n)/2的右边时,如图,[1,n-k1]和[k,n]完全相同,所以也是m^(n-k) 别忘了特判,当k等于n时,n-k为0,然后a1a1,a2a2,..anan,所以没什么限制,那么就是m^n AC代码: #includ…

Vue3 大屏数字滚动效果

父组件&#xff1a; <template> <div class"homePage"> <NumRoll v-for"(v, i) in numberList" :key"i" :number"v"></NumRoll> </div> </template> <script setup> import { onMounted, r…