【数据结构与算法】链表之美-复杂链表的复制与链表的插入排序

server/2024/11/29 8:59:34/

主页:HABUO🍁主页:HABUO

🍁如果再也不能见到你,祝你早安,午安,晚安🍁


1.复杂链表的复制

题目:请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null

示例1:

输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]

输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]

示例2:

输入:head = [[1,1],[2,1]]

输出:[[1,1],[2,1]]

分析:这是一个比较复杂的题目,我们只有见的多了之后我们可能看到这样新的题目,才会有敲门砖,又时候差的就是刚开始的敲门砖,敲门砖有了,剩下的就是用我们之前拥有的思想去实现就完了。因此呢,在这里我直接给出这个思路,以后再遇到这样类型的我们是不是就会了啊。首先,我们直接遍历是不是没办法搞啊,因为你random不知道指向谁,有些人可能会说,那我直接访问该节点的random不就行了,是的,但是如果我们链表当中,有两个相同值的节点怎么办?因为我们复制节点是复制它们的值和指向,地址是不是同等的复制下来,假如两个3,你只知道random指向3的那个节点,但是具体哪个节点我们是不是无能为力啊。因此这种直接的方法难以实现。好了,接下来很重要,1.我们为每一个节点备份一个节点,而且呢,让这个节点的next指向这个备份节点,让备份节点的next指向原链表该节点的next。2.让每一个备份节点的random指向它应该指向的备份节点,有人会问,那这咋指向,好关键点来了,你发现没有,备份节点的random是不是就是原节点的random的next啊,对不对,这就是这个题的关键。3.恢复原链表,让新链表链接起来,并返回头指针。这就是我们整体的思路。下面进行一步一步实现。

第一步:为每个节点进行备份,并且让这个节点的next指向这个备份节点,让备份节点的next指向原链表该节点的next,由于random画上之后错综复杂,不再指出,本题以示例1为导向。如下图所示:

代码实现如下:

//1.为每个链表节点备份一个节点Node* cur = head;while(cur){Node* copyNode = (Node*)malloc(sizeof(Node));copyNode->val = cur->val;copyNode->next = cur->next;cur->next = copyNode;cur = copyNode->next;}

第二步 让每一个备份节点的random指向它应该指向的备份节点,我们以13节点为例,看下图就会明白。因为每一个节点的备份节点是不是就是原节点的next因此我们想要备份节点指向它所指向的random,找到原节点的random是不是就行了啊。

代码实现如下:

//2.为备份节点的random进行链接cur = head;while(cur){//cur->random有可能指向NULLif(cur->random == NULL){cur->next->random = NULL;cur = cur->next->next;continue;}//每个备份节点的random,都是原节点的random的备份节点cur->next->random = cur->random->next;cur = cur->next->next;}

第三步,我们破坏了原链表的链接是不是需要恢复,而且新链表也还没有连接上,下面是不是就是恢复和链接,这里就只需要多设置几个指针来回的走就ok,前面的几道题的铺垫,相信这对于我们来说应该相对很简单。

//恢复原链表,把备份链表进行链接cur = head;Node* newHead = cur->next;Node* copyCur = newHead;Node* curNext = newHead->next;while(cur){cur->next = curNext;if(curNext == NULL){copyCur->next = NULL;break;}copyCur->next = curNext->next;copyCur = copyCur->next;cur = curNext;curNext = curNext->next->next;}return newHead;

要注意curNext是不是到最后就为NULL了,但是我们下面还有要访问它的next的代码,因此需要在这之前做一个判断语句做一个判断,防止错误。需要注意我们这种方法是不是对原链表进行修改了,这种操作是不太好的,等到好面我们学习的深入会用更优的方法去解决。

2.对链表进行插入排序

题目:给定单个链表的头 head ,使用 插入排序 对链表进行排序,并返回 排序后链表的头 。

插入排序 算法的步骤:

  1. 插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
  2. 每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
  3. 重复直到所有输入数据插入完为止。

示例1:

输入: head = [4,2,1,3]                 输出: [1,2,3,4]

示例2:

输入: head = [-1,5,3,4,0]              输出: [-1,0,3,4,5]

分析:我们可能直接的想法就是从第一个数据起,拿到一个数据就从头遍历放到合适的位置直至最后一个节点。 但是这是链表啊,这样操作是不是代价也太高了,要是数组你发到一个合适的位置,只需要将后面的数据往后移动即可,链表不太行。所以,我们的思路是,将第一个节点当作一个新链表的起始节点,之后从后面链表当中每次取一个节点,往新链表当中进行逐一比较进行插入,当然有可能头插,也有可能尾插,也可能中间插入,所以分三种情况,这就是我们的大致思想,具体如下图所示:

所以分三种情况进行插入,头插实现如下:注意我们一开始让head指向了head->next,因为我们让第一个节点当作新链表的头了

//头插if(cur->val >= head->val){head->next = cur;cur = head;newHead = cur;head =headNext;if(headNext == NULL)break;headNext = headNext->next;continue;}

中间插入实现代码如下:

while(cur){//中间插入if(cur->val < head->val){curPrev = cur;cur = cur->next;}else{head->next = cur;curPrev->next = head;head = headNext;if(headNext == NULL)break;headNext = headNext->next;break;}}

尾插的话是不是就是中间插入情况的cur指向了NULL还没找到合适的位置,因此实现代码如下:

//尾插if(cur == NULL){curPrev->next = head;head->next = NULL;head = headNext;if(headNext == NULL)break;headNext = headNext->next;}

所以将这些代码串起来放到一个循环当中是不是就实现了,因此完整代码如下:

 typedef struct ListNode Node;
struct ListNode* insertionSortList(struct ListNode* head) {if(head == NULL || head->next == NULL)return head;Node* newHead = head;head = head->next;newHead->next = NULL;Node* headNext = head->next;while(head) {Node* cur = newHead;Node* curPrev = NULL;//头插if(cur->val >= head->val){head->next = cur;cur = head;newHead = cur;head =headNext;if(headNext == NULL)break;headNext = headNext->next;continue;}while(cur){//中间插入if(cur->val < head->val){curPrev = cur;cur = cur->next;}else{head->next = cur;curPrev->next = head;head = headNext;if(headNext == NULL)break;headNext = headNext->next;break;}}//尾插if(cur == NULL){curPrev->next = head;head->next = NULL;head = headNext;if(headNext == NULL)break;headNext = headNext->next;}}return newHead;
}

 总结:本篇博客介绍了两个有关链表的两个题,相对于前面我们所介绍的题,可能有些难度,但这些难度是不是都是在思路上,如果有了这个思路实现起来是不是还是用我们前面的思想啊,因此我们别畏惧它,相信通过夜以继日的敲代码,我们肯定会拿到一个难题就会有思路的。大家加油!💯


🌻数据结构算法专栏🌻

🤜别放弃是我们的底色,加油!🤛

🏋不是每一粒种子都能开花,但播下种子就比荒芜的旷野强百倍🏋


http://www.ppmy.cn/server/145863.html

相关文章

多阶段报童问题动态规划求解,Python 实现

使用 python 编写了多阶段报童模型的动态规划算法。 使用了 python 的装饰器 dataclass &#xff0c;方便定义类尝试使用并行计算&#xff0c;没有成功&#xff0c;极易出错。动态规划中使用并行计算&#xff0c;还是挺有挑战的&#xff1b;而且并行计算不一定总是比非并行运算…

mfc110u.dll是什么意思,mfc110u.dll丢失解决方法大全详解

mfc110u.dll是Microsoft Foundation Classes (MFC)库的一个特定版本&#xff08;版本11.0&#xff09;的Unicode动态链接库文件。MFC是Microsoft为C开发者设计的一个应用程序框架&#xff0c;主要用于简化Windows应用程序的开发工作。这个框架封装了很多Windows API函数&#x…

Laravel8.5+微信小程序实现京东商城秒杀方案

一、商品秒杀涉及的知识点 鉴权策略封装掊口访问频次限制小程序设计页面防抖接口调用订单创建事务使用超卖防御 二、订单库存系统方案&#xff08;3种&#xff09; 下单减库存 优点是库存和订单的强一致性&#xff0c;商品不会卖超&#xff0c;但是可能导致恶意下单&#xff…

【技术支持】vscode不使用插件,两种方式重命名html标签对

1. 使用 VS Code 内置功能 VS Code 内置支持 HTML/XML 标签对的重命名功能。步骤如下&#xff1a; 将光标放置在标签名上&#xff08;如 <div> 或</div>&#xff09;。按下快捷键 F2&#xff08;重命名符号&#xff09;。输入新的标签名&#xff0c;按 Enter&…

动态内存管理的知识点笔记总结

开始之前&#xff0c;我们解释一为什么存在动态内存分配&#xff1f; 在我们之前写的&#xff1a; int arr[10]{0}; 连续开辟40个字节的空间 int a10; 在内存开辟4个字节 但是&#xff0c; 1.这种大小是固定死的&#xff0c;我们是无法改变的。 2. 数组在申明的时候&a…

C++和C中的volatile 关键字

在 C/C 中volatile 关键字的作用 1.防止编译器优化 编译器在编译程序时&#xff0c;为了提高程序的执行效率&#xff0c;会对代码进行优化。例如&#xff0c;当编译器发现一个变量的值在一段代码中没有被显式地改变时&#xff0c;它可能会将这个变量的值缓存到寄存器中&#…

AWS海外注册域名是否需要实名认证?

在全球化的互联网环境中&#xff0c;注册域名已成为企业和个人建立在线存在的重要步骤。亚马逊网络服务&#xff08;AWS&#xff09;作为全球领先的云服务提供商&#xff0c;其域名注册服务也备受关注。然而&#xff0c;对于在AWS上注册海外域名是否需要实名认证&#xff0c;许…

javax.net.ssl.SSLHandshakeException: Received fatal alert: protocol_version

查了一上午&#xff0c;这个错误的原因好像是 发送端和接收端采用的 TLS 协议版本不一致导致的&#xff1a; 解决步骤 确认双方使用的 TLS 协议版本更改一方的 TLS 使两方相同 1.确认双方使用的 TLS 协议版本 捣鼓了半天&#xff0c;终于发现一个简单靠谱的方法来确认双方的…