链表OJ--超详细解析

链表OJ

文章目录

  • 链表OJ
    • 1. 反转链表
    • 2. 返回K值
    • 3. 链表的中间节点
    • 4. 回文链表
    • 5. 相交链表
    • 6. 带环链表
      • 6.1 为什么一定会相遇,有没有可能会错过,或者出现永远追不上的情况,请证明
      • 6.2 slow一次走一步,fast如果一次走3步,走4步,走5步还能追上吗,请证明
    • 7. 带环链表2
      • 7.1 为什么它们最终肯定会在入口点的位置相遇,请证明
    • 8. 复制链表
    • 结语

1. 反转链表

OJ链接:206. 反转链表 - 力扣(LeetCode)

好的,我们一起来看一下题目,题目是这样说的

在这里插入图片描述

思路一:可以新创建一个链表,然后采用头插的方法,从 1 一个接一个的头插数据,这种是最容易想到的方法,但是要开辟一块空间,需要考虑空间复杂度的问题

思路二:使用 n1 n2 n3 三个指针,首先我们让 n1 指向空, n2 指向头节点, n3 指向头节点的下一个节点
在这里插入图片描述

然后我们再让 n1 指向 n2n2 指向 n3 ,就变成了这样

在这里插入图片描述

这样的话 n1 是不是指向反转链表之后的尾节点,那我们如果再让 n1 指向 n2n2 指向 n3 ,把整个链表都给遍历一遍之后,是不是就把整个链表给反转了,对吧,像这样

在这里插入图片描述

然后我们在考虑遍历截止的条件,哎,是不是 n2 为空了之后遍历就截止了,最后再返回 n1 就好了

整体的思路是不是都比较清楚了,接下来就是代码实现了,这个就得多练习了

struct ListNode* reverseList(struct ListNode* head) {
    if(head == NULL)
    {
        return NULL;
    }
    struct ListNode* n1 = NULL,*n2 = head,*n3 = n2->next;
    while(n2)
    {
        n2->next = n1;
        n1 = n2;
        n2 = n3;
        if(n3)
            n3 = n3->next;
    }
    return n1;
}

2. 返回K值

OJ链接:面试题 02.02. 返回倒数第 k 个节点 - 力扣(LeetCode)

好的,我们一起来看一下题目,题目是这样说的

在这里插入图片描述

思路一:我们可以创建一个数组,把链表中的数据存放在数组里,用数组来做,不过要再开辟一块空间,还要考虑空间复杂度的问题

思路二:反转链表遍历一下,再找第K个节点,这样也是可行的

思路三:使用快慢指针的方法,我们先让快指针走K步,然后再让快慢指针一起走,就能发现当快指针指向空的时候,我们的慢指针指向的就是倒数第K个节点,最后返回慢指针节点的值就行了

在这里插入图片描述

代码实现:

int kthToLast(struct ListNode* head, int k){
    struct ListNode* fast = head,*slow = head;
    while(k--)
    {
        fast = fast->next;
    } 
    while(fast)
    {
        slow = slow->next;
        fast = fast->next;
    }
    return slow->val;
}

3. 链表的中间节点

OJ链接:876. 链表的中间结点 - 力扣(LeetCode)

好的,我们一起来看一下题目,题目是这样说的

在这里插入图片描述

思路:主要的思路还是我们的快慢指针,这时就要分两种情况,一个是奇数的情况,另一个是偶数的情况

  • 当链表为奇数时:首先我们要初始化两个快慢指针,然后通常让慢指针走一步,快指针走两步,像这样

在这里插入图片描述

像这样,当快指针指向尾节点的时候,慢指针就刚好指向中间节点,最后返回慢指针节点的值

  • 当链表为奇数时:首先我们还要初始化两个快慢指针,然后还是让慢指针走一步,快指针走两步,像这样
    在这里插入图片描述

就像这样,和前者不同的是,当快指针指向空时,慢指针才指向中间节点,最后返回慢指针节点的值

思路都清楚了吧,接下来就是代码实现:

struct ListNode* middleNode(struct ListNode* head) {
    struct ListNode* fast = head,*slow = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
    }
    return slow;
}

4. 回文链表

OJ链接:234. 回文链表 - 力扣(LeetCode)

好的,我们一起来看一下题目,题目是这样说的

在这里插入图片描述

我们要想证明它是个回文结构,首先我们要先知道回文结构是什么,从前往后和从后往前是完全相同的以中间节点为中心这个链表是对称的,举个例子比如12321和1221这两个都是回文,如果是123123这个还是回文吗,这种就不是回文了

思路:我们可以先找中间节点,找中间节点有什么好处呢,当我们判断链表是否是回文链表,是不是首先寻找中间节点,中间节点断开然后再从两头对应,要是两头节点的值都能一一对应,我们就说这个链表是回文链表,我们找到中间节点之后,然后从中间节点断开,将中间节点后面的链表反转,这样就可以一一比较,怎么比呢,一个从头节点开始,另一个从中间节点开始,一一比较,就可以了,都相等就是回文链表,只要有一个不相等,就不是回文链表

现在我们思考一个问题,需要分情况吗,很简单,画个图就知道了

在这里插入图片描述

代码实现:

 struct ListNode* middleNode(struct ListNode* head) {
    struct ListNode* fast = head,*slow = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
    }
    return slow;
}
 struct ListNode* reverseList(struct ListNode* head) {
    if(head == NULL)
    {
        return NULL;
    }
    struct ListNode* n1 = NULL,*n2 = head,*n3 = n2->next;
    while(n2)
    {
        n2->next = n1;
        n1 = n2;
        n2 = n3;
        if(n3)
            n3 = n3->next;
    }
    return n1;
}
bool isPalindrome(struct ListNode* head) {
    struct ListNode* mid = middleNode(head);
    struct ListNode* rmid = reverseList(mid);
    struct ListNode* cur = head; 
    while(rmid)
    {
        if(cur->val != rmid->val)
        {
            return false;
            break;
        }
        cur = cur->next;
        rmid = rmid->next;
    }
    return true;
}

5. 相交链表

OJ链接:160. 相交链表 - 力扣(LeetCode)

好的,我们一起来看一下题目,题目是这样说的

在这里插入图片描述

思路一:我们首先要有一个共识就是,链表相等不一定相交,这个大家一定要清楚这一点,我们认为如果两个链表相交,那么他们的地址是相同的,而不是数据是相等的,像这样,我们需要注意一下

在这里插入图片描述

我们可以一个一个节点进行比较,将链表A的所有节点和链表B的所有节点,但是我们想想时间复杂度是多少,是不是O(n*2),不满足题意

思路二:首先我们要做的是判断两个链表是否相交,再找出交点,我们大致的思路就是这样,那我们应该怎么判断呢,怎么找呢

  • 哎,我们把两个链表都遍历一下,如果它们的尾节点指向的位置相同是不是两个链表就一定会相交,这就解决了如何判断两个链表是否会相交的问题
  • 那我们知道两个链表相交之后,如何找交点呢,我们想一想如果我们让两个指针都指向头节点,然后再同时遍历的话,是不是有可能会错过啊,就像这样

在这里插入图片描述

  • 是不是啊,说的没有问题吧,那怎么办呢,看来大家都很聪明,已经发现了,如果我们让 pb 先走一步,让它们的起始位置都一样,然后再让它们一步一步地走就可以相遇了,像这样

在这里插入图片描述

哦,也就是说,我们先让长一些指针走,走它们的差值步,使两个指针指向的位置相同,再让两个指针一步一步往下走,就可以找到交点了,这个思路还是很清晰的,接下来就是代码实现了

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    struct ListNode* pa = headA,*pb = headB;
    int lenA = 1,lenB = 1;
    while(pa)
    {
        pa = pa->next;
        lenA++;
    }
    while(pb)
    {
        pb = pb->next;
        lenB++;
    }
    if(pa != pb)
    {
        return NULL;
    }
    int gap = abs(lenA - lenB);
    //假设法
    struct ListNode* plong = headA,*pshort = headB;
    if(lenA < lenB)
    {
        plong = headB;
        pshort = headA;
    }
    while(gap--)
    {
        plong = plong->next;
    }
    while(plong != pshort)
    {
        plong = plong->next;
        pshort = pshort->next;
    }
    return plong;
}

6. 带环链表

OJ链接:141. 环形链表 - 力扣(LeetCode)

在这里插入图片描述

思路:主要的思路还是我们的快慢指针,也就是让慢指针走一步,快指针走两步,先让两个指针从头节点的位置出发如果带环的话,一定会在环中相遇,否则的话,快指针就先指向尾节点,画个图就能更好的理解

在这里插入图片描述

理顺思路之后,代码就很好写的

bool hasCycle(struct ListNode *head) {
    struct ListNode* fast = head,*slow = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;

        if(slow == fast)
        {
            return true;
        }
    }
    return false;
}

好了,这个题还是非常简单的,那么在我们面试的时候,面试官可能会问我们两个问题

6.1 为什么一定会相遇,有没有可能会错过,或者出现永远追不上的情况,请证明

证明:假设链表带环,两个指针最后都会进入环,快指针先进环,慢指针后进环。当慢指针刚进环时,可能就和快指针相遇了,这是最好情况,那最最差情况下两个指针之间的距离刚好就是环的长度。此时,两个指针每移动 一次之间的距离就缩小一步,不会出现每次刚好是套圈的情况,因此:在满指针走到一圈之前,快指针肯定是可以追上慢指针的,即相遇

在这里插入图片描述

6.2 slow一次走一步,fast如果一次走3步,走4步,走5步还能追上吗,请证明

证明:我们假设 fast 一次走3步,当 slow 进环时,和 fast 距离是N,追击过程距离变化如下图所示:

在这里插入图片描述

我们可以很明显的看到,当N为偶数时,就能追击上,当N为奇数时,最后值为-1就表明已经错过了,要开始进行新一轮的追击

接下来我们在考虑新一轮追击距离变成了多少,是不是C-1(假设C为环的长度)

我们想一想是不是也要分两种情况当C-1为偶数时,就能追击上,当C-1为奇数时,最后值还是为-1,就永远追不上

小结一下:

  1. N是偶数,第一轮就追上了

  2. N是奇数,第一轮追击会错过,距离变成C-1

    a. 如果C-1是偶数,下一轮就追上了

    b. 如果C-1是奇数,那么就永远追不上

那我们在思考一下,b成立的条件,是不是永远就追不上呢,我们发现当N为奇数且C为偶数时,结论成立,我们刚在是逻辑上想的,接下来证明一下,看看是不是就永远追不上

假设slow进环时,fast与slow的距离是N

假设slow进环时,fast已经走了x圈

设slow走过的距离为 L

fast走过的距离就是 L+x*C+C-N

那么我们又说了,fast走的距离是slow距离的3倍

就可以非常轻松地写出 3L = L+x*C+C-N

化简一下就是 *2L = (x-1)C-N

哦,这就非常明显了,等式左边一定是偶数,那就说明等式右边也一定是偶数

  • 我们说当N为偶数的话,那么C只能为偶数,(x-1)*C才为偶数
  • 当N为奇数的话,那么C只能为奇数,只有奇数-奇数才能是偶数

那就说明当N为奇数且C为偶数时,条件不成立,所以永远追不上的假设不成立,一定会追上的

结论

  • N是偶数,第一轮就追上了
  • N是奇数,第一轮追不上,C-1是偶数第二轮就可以追上

7. 带环链表2

OJ链接:142. 环形链表 II - 力扣(LeetCode)

在这里插入图片描述

思路:主要的思路还是我们的快慢指针,slow走一步fast走两步,当它们在环里相遇时,让一个指针从链表起始位置开始遍历链表,同时让一个指针从判环时相遇点的位置开始绕环运行,两个指针都是每次均走一步,最终肯定会在入口点的位置相遇。

思路很简单,看看代码:

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

通常这个时候面试官会问一个问题,这个问题和我们一样

7.1 为什么它们最终肯定会在入口点的位置相遇,请证明

证明:假设slow和fast已经相遇了,设初始位置到入口点的距离为 L

设入口点到相遇点的距离为 N ,环的长度为 C

假设slow相遇时,fast已经走了x圈

那么fast走过的距离为 L+x*C+N

slow走过的距离为 L+N

思考一个问题我们slow与fast相遇的时候,slow一定走不完一圈

很简单,如果slow走完一圈的话,fast是不是就走完两圈了,是不是早就可以相遇了

fast走过的距离是slow走过距离的两倍

也就是 **L+x * C+N = 2 * (L+N) **

化简一下也就是 *L = (x-1)C + C-N

假如x=1,那么L=C-N,正好是相遇点到进环点的距离与入环之前的距离相等,让一个节点从头开始遍历,一个从相遇点开始,最终在入环的第一个节点相遇,证明了我们肯定会在入口点的位置相遇,如果x不唯1呢,那也会相遇,无非就是在环里多走了几圈,最后还是会在入环的第一个节点相遇

对大家来说这些都很EZ,对不对,接下来的最后一道OJ题就是比较综合的了

8. 复制链表

OJ链接:138. 随机链表的复制 - 力扣(LeetCode)

在这里插入图片描述

题干中提到了一个深拷贝的概念,深拷贝是拷贝一个值和指针指向跟当前链表一模一样的链表

思路一:这道链表题每个节点里多了个指针指向随机节点,也有可能指向空,然后深拷贝一份,如果我们直接遍历然后拷贝呢?硬拷贝是可以的,但是有个问题,随机指针(random)指向的值如何在新链表中实现呢,如果我们去链表中查找,那么万一有重复的值怎么办呢,这就会导致没拷贝成功,如果真要用这种暴力查找,就要算相对位置,而且它的时间复杂度为O(N^2),还需要考虑时间复杂度的问题

思路二:我们这个题的主要思路应该是先拷贝链表,然后把拷贝的链表插入到原节点的后面,每个拷贝节点和原节点建立了一个关联关系

在这里插入图片描述

接下来就是要考虑 random 的问题了,如下图所示,我们7的 random 指向的为空,那么我们拷贝节点7的 random 也要指问空,我们拷贝节点7就在原节点的后面,所以处理起来很方便,那么13的 random 指向的是7我们如何拷贝呢,拷贝节点的random指向的就是 cur->random>next ,最后再将拷贝节点拿下来尾插,成为一个新的链表,虽然我们破坏了原链表的结构,我们可以选择恢复原链表也可以不恢复,就要看看题目的要求

在这里插入图片描述

代码如下:

typedef struct Node Node;
struct Node* copyRandomList(struct Node* head) {
    Node* cur = head;
    // 拷贝节点插入在原节点后面
    while (cur)
    {
        Node* copy = (Node*)malloc(sizeof(Node));
        copy->val = cur->val;
        copy->next = cur->next;
        cur->next = copy;
        cur = copy->next;
    }
    //控制random
    cur = head;
    while (cur)
    {
        Node* copy = cur->next;
        if (cur->random == NULL)
        {
            copy->random = NULL;
        }
        else
        {
            copy->random = cur->random->next;
        }
        cur = copy->next;
    }
    //拷贝节点取下来尾插成为新链表,然后恢复原链表(不恢复也可以)
    Node* copyhead = NULL, * copytail = NULL;
    cur = head;
    while (cur)
    {
        Node* copy = cur->next;
        Node* next = copy->next;
        if (copytail == NULL)
        {
            copyhead = copytail = copy;
        }
        else
        {
            copytail->next = copy;
            copytail = copytail->next;
        }
        //恢复原链表
        cur->next = next;
        cur = next;
    }
    return copyhead;
}

结语

好了,感谢你能看到这里,本篇到这就结束了,希望对你有所帮助

溜了溜了,我们下篇文章再见吧

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

解决nvm切换node版本后,全局依赖无法使用

问题描述 使用 nvm install 10.24.1 安装node版本&#xff0c;安装成功后&#xff0c;使用 npm install -g xxx 安装全局依赖&#xff08;私有库&#xff09;&#xff0c;安装成功后&#xff0c;运行命令提示找不到命令。 已做以下尝试 npm root -g&#xff0c;返回 D:\Prog…

【Java面试】二十、JVM篇(上):JVM结构

文章目录 1、JVM2、程序计数器3、堆4、栈4.1 垃圾回收是否涉及栈内存4.2 栈内存分配越大越好吗4.3 方法内的局部变量是否线程安全吗4.4 栈内存溢出的情况4.5 堆和栈的区别是什么 5、方法区5.1 常量池5.2 运行时常量池 6、直接内存 1、JVM Java源码编译成class字节码后&#xf…

window端口占用情况及state解析

背景&#xff1a; 在电脑使用过程中&#xff0c;经常会开许多项目&#xff0c;慢慢地发现电脑越来越卡&#xff0c;都不知道到底是在跑什么项目导致&#xff0c;于是就想查看一下电脑到底在跑什么软件和项目&#xff0c;以作记录。 常用命令 netstat -tuln &#xff1a; 使用…

这些已经死去的软件,依旧无可替代

互联网这条长河里&#xff0c;软件们就像流星一样&#xff0c;一闪而过。有的软件火过一段时间&#xff0c;然后就慢慢消失了。 说不定有些软件你以前天天用&#xff0c;但不知道从什么时候开始就不再用了。时间一天天过去&#xff0c;我们的热情、记忆都在消退&#xff0c;还…

【免费API推荐】: 解锁创意无限,享受免费开发之旅

幂简网站上免费的 API 分类内汇集了各种各样的免费 API&#xff0c;涵盖了多个领域和功能。无论你是在构建网站、开发应用还是进行数据分析&#xff0c;这个项目都能为你提供丰富的选择。 幂简集成搜集了网络上免费的 API 资源&#xff0c;为广大开发者和创业者提供便捷的访问渠…

在Linux中安装中文编程语言洛书

本次安装使用的VMware中的Ubuntu系统虚拟机&#xff0c;尝试下中文编程。 安装洛书 下载官网&#xff1a;洛书——打造开源高效强大的国产编程语言 官方文档&#xff1a;洛书文档中心 (losu.tech) 点击获取 在终端中安装工具 dpkg和rlwrap&#xff1a; sudo apt install d…

服务器数据恢复—NTFS文件系统下双循环riad5数据恢复案例

服务器存储数据恢复环境&#xff1a; EMC CX4-480存储&#xff0c;该存储中有10块硬盘&#xff0c;其中有3块磁盘为掉线磁盘&#xff0c;另外7块磁盘组成一组RAID5磁盘阵列。运维人员在处理掉线磁盘时只添加新的硬盘做rebuild&#xff0c;并没有将掉线的硬盘拔掉&#xff0c;所…

《失败的逻辑》|别再无效复盘了!学会认清每一次失败的必然性

为什么铁路信号系统工作正常时&#xff0c;列车仍然会发生撞车事故&#xff1f; 为什么所有操作人员都警觉地坚守着工作岗位&#xff0c;核反应堆依然会发生灾难性的熔化事故&#xff1f; 为什么我们制定得甚好的那么多专业和个人计划&#xff0c;会如此频繁地出岔子&#xff1…

RoaringBitMap处理海量数据内存diff

一、背景 假设mysql库中有一张近千万的客户信息表(未分表)&#xff0c;其中有客户性别&#xff0c;等级(10个等级)&#xff0c;参与某某活动等字段 1、如果要通过等级性别其他条件(离散度也低)筛选出客户&#xff0c;如何处理查询&#xff1f; 2、参与活动是记录活动ID&#…

NVIDIA新模型Nemotron-4:98%的训练数据是合成生成的,你敢信?

获取本文论文原文PDF&#xff0c;请公众号 AI论文解读 留言&#xff1a;论文解读 标题&#xff1a;Nemotron-4 340B Technical Report 模型概述&#xff1a;Nemotron-4 340B系列模型的基本构成 Nemotron-4 340B系列模型包括三个主要版本&#xff1a;Nemotron-4-340B-Base、…

RNN的变种们:GRULSTM双向RNN

上篇笔记记录到RNN的一个缺点&#xff1a;训练时会出现梯度消失&#xff0c;解决的办法是找到一个更优的计算单元。这里也有GRU和LSTM。 GRU&#xff08;Gated Recurrent Unit&#xff09;门控训练网络 什么是门控机制&#xff1f;就是对当前的输入进行一个筛选。门打开&…

《UNIX环境高级编程》第三版(电子工业出版社出品)——两年磨一剑的匠心译作

历时两年&#xff0c;《UNIX环境高级编程》的翻译工作终于落下帷幕。这一路走来&#xff0c;真可谓是如鱼饮水&#xff0c;冷暖自知。还记得最初看到招募译者消息的那一刻&#xff0c;内心的激动难以言表。我毫不犹豫地报名&#xff0c;而后经历了试译、海选等激烈的角逐&#…

「TCP 重要机制」滑动窗口 粘包问题 异常情况处理

&#x1f387;个人主页&#xff1a;Ice_Sugar_7 &#x1f387;所属专栏&#xff1a;计网 &#x1f387;欢迎点赞收藏加关注哦&#xff01; 滑动窗口&粘包问题&异常情况处理 &#x1f349;滑动窗口&#x1f34c;流量控制&#x1f34c;拥塞控制&#x1f34c;延时应答&…

mkv文件怎么转成mp4?教你四种常见的转换方法!

mkv文件怎么转成mp4&#xff1f;大家在使用mkv文件的时候有没有遇到过下面这些缺点&#xff0c;首先是mkv的兼容性不行&#xff0c;这体验在它不方便分享上面&#xff0c;很有可能我们分享出去但是对方根本无法进行接受&#xff0c;这就导致我们需要进行额外的操作才能分享&…

轻轻松松上手的LangChain学习说明书

本文为笔者学习LangChain时对官方文档以及一系列资料进行一些总结&#xff5e;覆盖对Langchain的核心六大模块的理解与核心使用方法&#xff0c;全文篇幅较长&#xff0c;共计50000字&#xff0c;可先码住辅助用于学习Langchain。 一、Langchain是什么&#xff1f; 如今各类AI…

pip导出格式错乱问题

pip导出带有各种路径 pip只导出版本 pip list | tail -n 3 | awk {print $1""$2} > requirements.txt

kettle从入门到精通 第七十一课 ETL之kettle 再谈http post,轻松掌握body中传递json参数

场景&#xff1a; kettle中http post步骤如何发送http请求且传递body参数&#xff1f; 解决方案&#xff1a; http post步骤中直接设置Request entity field字段即可。 1、手边没有现成的post接口&#xff0c;索性用python搭建一个简单的接口&#xff0c;关键代码如下&#…

6-18作业

作业1&#xff1a; mywidget.h #ifndef MYWIDGET_H #define MYWIDGET_H#include <QWidget> #include <QLabel> #include <QMessageBox>QT_BEGIN_NAMESPACE namespace Ui { class myWidget; } QT_END_NAMESPACEclass myWidget : public QWidget {Q_OBJECTpu…

Oracle基本语法

前言&#xff1a; 1.使用的数据库不同&#xff0c;所使用的语法也略有不同 2.SQL对大小写不敏感&#xff0c;无论大小写&#xff0c;sql自动转换为大写 3.用户名、表名、表空间名、文件路径......等需要用单引号将其包含 4.一般引号里面的内容需要大写 准备工作&#xff1a; &a…

NocoBase调研

项目概述&#xff1a; nocobase是一个开源的无代码和低代码开发平台&#xff0c;允许用户快速部署私有、可控、易于扩展的系统。 NocoBase官网&#xff1a;NocoBase-开源、私有部署的轻量级无代码和低代码开发平台 核心特性&#xff1a; 强调NocoBase的数据模型驱动方法&am…