数据结构--链表进阶面试题

        在链表题目开始之前我们来复习一道数组元素的逆序问题:

        给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。

提示:

  • 1 <= nums.length <= 10^5
  • -2^31 <= nums[i] <= 2^31 - 1
  • 0 <= k <= 10^5

 思路1:旋转k次,每一次项右轮转一个元素。时间复杂度O(N^2)。空间复杂度O(1)。

 思路2:创建一个新的数组,现将原数组的后k个元素放置到新数组中,然后再把剩余元素依次放入新数组中,最后把新数组的元素按顺序放回原数组。时间复杂度O(N)。空间复杂度O(N)。

上面两种思路各有优势,也有缺点。我们再来看看思路三。

思路3:先逆序前n-k个元素,再逆序后k个元素,最后整体逆序。这种方法在时间、空间复杂度上都是最优的,但是思路不好想到,这就要大家多多积累,我们来看看代码:

void revese(int* nums,int left ,int right)
{while(left <= right){int tmp = nums[left];nums[left] = nums[right];nums[right] = tmp;left++;right--;}}void rotate(int* nums, int numsSize, int k) 
{k %= numsSize;revese(nums,0,numsSize-k-1);revese(nums,numsSize-k,numsSize-1);revese(nums,0,numsSize-1);
}

例1:返回链表的倒数第k个节点

        实现一种算法,找出单向链表中倒数第 k 个节点。返回该节点的值。给定的 k 保证是有效的。

解:

        这到题的思路和之前的快慢指针相似,我称之为先后指针,我们先创建两个指针(fast、slow),既然是找倒数第k个节点,那我们就先让fast走k个节点,然后让两个指针同时向后走,当fast指针走到尾节点时,slow节点就是倒数第k个节点。代码如下:

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

 例2:链表的回文结构

        对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。

解:

        这道题有些难度,不过我们使用之前学的解法思路可以很轻松的过这道题。既然是回文结构,那我们就先找到他的中间节点,然后我们在逆序后半段链表,然后再比较原头结点和逆序后的头结点的值,如果不相等就返回false,反之继续向后遍历,直到其中有一个指针指向为空,返回true。

 

bool chkPalindrome(ListNode* A) {//寻找中间节点struct ListNode * fast = A;struct ListNode * slow = A;while(fast && fast->next){slow = slow->next;fast = fast->next->next;}//翻转后半链表:头插法struct ListNode* pcur = slow;struct ListNode* newhead = NULL;while(pcur){struct ListNode* next = pcur->next;pcur->next = newhead;newhead = pcur;pcur = next;}//向后遍历while(A && newhead){if(A->val != newhead->val){return false;}A = A->next;newhead = newhead->next;}return true;}

例3:相交链表

        给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。

题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须 保持其原始结构 。

提示:

  • listA 中节点数目为 m
  • listB 中节点数目为 n
  • 1 <= m, n <= 3 * 10^4
  • 1 <= Node.val <= 10^5
  • 0 <= skipA <= m
  • 0 <= skipB <= n
  • 如果 listA 和 listB 没有交点,intersectVal 为 0
  • 如果 listA 和 listB 有交点,intersectVal == listA[skipA] == listB[skipB]

图示两个链表在节点 c1 开始相交

自定义评测:

评测系统 的输入如下(你设计的程序 不适用 此输入):

  • intersectVal - 相交的起始节点的值。如果不存在相交节点,这一值为 0
  • listA - 第一个链表
  • listB - 第二个链表
  • skipA - 在 listA 中(从头节点开始)跳到交叉节点的节点数
  • skipB - 在 listB 中(从头节点开始)跳到交叉节点的节点数

评测系统将根据这些输入创建链式数据结构,并将两个头节点 headA 和 headB 传递给你的程序。如果程序能够正确返回相交节点,那么你的解决方案将被 视作正确答案 。

 解:

        本道题在初见可能会没有思路,因为我们没有办法确定他们是否相交了或者走到哪个节点了。但是仔细思考,我们还是有办法去处理这些情况的。

        首先,我们要确定两个链表是否真的相交了,那我们可以先遍历两个数组,如果它们的尾节点为同一个,说明它们是相交的,反之即为不想交,返回空指针。

        如果是相交的话,我们就要重新去寻找它们的第一个相交节点,我们可以在第一次遍历判断他们是否相交时,顺便计算一下两个链表的节点个数,这样不需要重新再去计算,降低了时间复杂度。如果A链表长,那就让A先走len(A) - len(B)和节点,然后让两个节点从所在位置继续向后遍历,当两个节点相等时,就找到了第一个相交节点,返回该节点即可。

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     struct ListNode *next;* };*/typedef struct ListNode ListNode;
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) 
{ListNode* cur1 = headA;ListNode* cur2 = headB;int len1 = 1;int len2 = 1;while(cur1->next){cur1 = cur1->next;len1++;}while(cur2->next){cur2 = cur2->next;len2++;}if(cur1 != cur2){return NULL;}else{ListNode* pcur1 = headA;ListNode* pcur2 = headB;if(len1 >= len2){int num = len1 - len2;while(num--){pcur1 = pcur1->next;}}else{int num = len2 - len1;while(num--){pcur2 = pcur2->next;}}while(pcur1 != pcur2){pcur1 = pcur1->next;pcur2 = pcur2->next;}return pcur1;}
}

 例4:随机链表的复制

        给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。

        构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 

        例如,如果原链表中有 X 和 Y 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random --> y 。

        返回复制链表的头节点。用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val,random_index] 表示:

  • val:一个表示 Node.val 的整数。
  • random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为  null 。

你的代码  接受原链表的头节点 head 作为传入参数。

 解:

        本题的难点在于节点的随机指针random难以确定指向的方位。所以需要我们另辟蹊径,我们可以直接在原链表的每个节点之后复制一个相同的节点,这样我们就可以直到该新节点的随机节点的位置在原节点的随机节点的下一个位置。怎么样,是不是很巧妙。

        首先我们还是先判断原链表是否为空,为空就返回NULL。反之,我们就要开始插入复制节点了,各位要注意,新复制的原点的随机节点在第一遍遍历的时候都赋值为NULL,因为,第一遍还没有创建好所有的节点,你可能找不到相应的随机节点。第二遍遍历就是单纯把所有的random都指向他们相应的节点。第三遍遍历为的是还原原链表并分离新链表。代码如下:

/*** Definition for a Node.* struct Node {*     int val;*     struct Node *next;*     struct Node *random;* };*/
typedef struct Node Node;
Node* copyRandomList(Node* head) 
{if (head == NULL) return NULL;// 第一遍遍历:在每个原节点后面插入复制节点struct Node *cur = head;while (cur != NULL) {struct Node *copyNode = (struct Node *)malloc(sizeof(struct Node));//固定位置插入copyNode->val = cur->val;copyNode->next = cur->next;//随机指针先置为空,后续节点完全创建好之后再进行复制copyNode->random = NULL;cur->next = copyNode;cur = copyNode->next;}// 第二遍遍历:设置复制节点的 random 指针cur = head;while (cur != NULL) {//NULL已经赋值过了,所以无需再次复制if (cur->random != NULL) {cur->next->random = cur->random->next;}cur = cur->next->next;}// 第三遍遍历:将复制节点从原链表中分离出来//分离新链表的同时,还原原链表cur = head;struct Node *newHead = head->next;struct Node *newCur = newHead;while (cur != NULL)     {cur->next = cur->next->next;if (newCur->next != NULL) {newCur->next = newCur->next->next;}cur = cur->next;newCur = newCur->next;}return newHead;
}

例5:环形链表

        给你一个链表的头节点 head ,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。

如果链表中存在环 ,则返回 true 。 否则,返回 false 。

提示:

  • 链表中节点的数目范围是 [0, 10^4]
  • -10^5 <= Node.val <= 10^5
  • pos 为 -1 或者链表中的一个 有效索引 。

解:

         这道题不是很难,但是用到了很多数理逻辑思维。首先我们要确定链表中是否存在环,我们先假设有环,那如何确定环的存在也是一个问题。我们来看一张图:

        我们依旧使用快慢指针来解题,不了解的可以看前两篇文章。slow走一步,fast走两步,直到slow进入环中,fast开始追击slow,因为fast每次都比slow多走一步,那是不是说明fast早晚都可以追上slow呀。既然能追上,是不是就说明有环存在,这道题就解了。如果没有环,两个指针就不可能相遇,我们在里面判断一下就可以了。代码如下:

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     struct ListNode *next;* };*/typedef struct ListNode ListNode;
bool hasCycle(struct ListNode *head) 
{//判断是否为空链表if(head == NULL){return false;}ListNode* fast = head;ListNode* slow = head;//循环中如果fast和slow相等说明成环,不成环在循环结束后会返回faslewhile(fast && fast->next){fast = fast->next->next;slow = slow->next;if(fast == slow){return true;}}return false;
}

例5拓展:

        我们要讨论的远远不止这些,如果slow固定为走一步,但是fast不是走两步,而是走三步、四步、五步……呢?下面我们来一个一个讨论:

        我们假设进环前的节点个数为L,环的节点个数为C,当slow进环时,fast与slow相距N。

        我们先来看fast走三步的情况, 每移动一次指针,fast和slow的距离就会减少2,如果N为偶数,过程为N、N-2、N-4、……、2、0,最后两个指针会相遇;如果N为奇数,过程为N、N-2、N-4、……、3、1、-1,我们会发现两个指针错过了,并且越来越远,我们就需要重新计算它们的距离。错过之后它们的距离变成了C-1,当C-1为偶数时,它们最终会相遇对吧;但是当C-1为奇数时,它们就永远不会相遇了。那这里是不是就无解了呢,我们继续往下去探讨:

        我们假设slow走的路程是L,那么fast走的就是L + x*C + C-N(x为正整数)。我们还知道fast走的路程是slow的三倍,那么我们就可以写出关系式:3L = L + x*C + C-N。化简之后我们可以得到2L = (x+1)*C -N。由于C-1是奇数,仔细看你会发现:偶数 = 任意整数 * 偶数 - 奇数。这是一个恒不成立的等式,也就是说这种情况不存在。那我们是不是就可以证明,fast必定可以追上slow。

        看完三步后我们再来看看四步的,每移动一次指针,fast和slow的距离就会减少3,这时我们要分三种情况去看:N%3==0时,过程为N、N-3、N-6、……、3、0,可以相遇;N%3==1时,过程N、N-3、N-6、……、4、1、C-2;当N%3==2时,过程为N、N-3、N-6、……、5、2、C-1。后面的过程和三步的相似,各位感兴趣可以自己去算一下,这里不过多着笔。

        从这两种情况中我们要学的是数理逻辑思维,在正式工作时很少用到,但是在面试时可能会被问,大家注意一下就可以了。

例6:环形链表二

        给定一个链表的头节点  head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

        如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

不允许修改 链表。

提示:

  • 链表中节点的数目范围在范围 [0, 10^4] 内
  • -10^5 <= Node.val <= 10^5
  • pos 的值为 -1 或者链表中的一个有效索引

解:

        这道题用上面的思路可以很快就解决, 我们使用两步fast的快慢指针来解决。

 

        首先,我们让fast和slow走到相遇节点,记为meet。我们来算一下,假设slow走了L+N ,fast走了L + x*C + N。由关系可得,2(L+N) = L + x*C + N。化简得L = (x-1)*C + C-N。其中C是环的节点数,C-N就是meet到入环节点的距离,因为(x-1)是可以等于0的,我们惊喜地发现,L = C-N。也就是说在头指针和meet一起向后走,会同时到达我们要的节点。代码如下:

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     struct ListNode *next;* };*/typedef struct ListNode ListNode;
ListNode *detectCycle(struct ListNode *head) 
{ListNode* fast = head;ListNode* slow = head;ListNode* meet = NULL;//相遇while(fast && fast->next){slow =  slow->next;fast = fast->next->next;if(fast == slow){meet = fast;break;}}//判断链表是否无环if(fast == NULL || fast->next == NULL){return NULL;}//返回入环的第一个节点ListNode* pcur = head;while(pcur != meet){pcur = pcur->next;meet = meet->next;}return meet;}

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

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

相关文章

wordpress子比主题给文章内容加上密码查看

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、pandas是什么?二、使用步骤1.引入库2.读入数据第三步:文章内添加代码前言 提示:这里可以添加本文要记录的大概内容: 例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,…

Docker容器:Docker-Consul的容器服务更新与发现

目录 前言 一、什么是服务注册与发现 二、 Docker-Consul 概述 1、Consul 概念 2、Consul 提供的一些关键特性 3、Consul 的优缺点 4、传统模式与自动发现注册模式的区别 4.1 传统模式 4.2 自动发现注册模式 5、Consul 核心组件 5.1 Consul-Template组件 5.2 Consu…

一款 NodeJS 版本管理工具 NVM (Windows)

一、简介 Node Version Manager&#xff08;NVM&#xff09;是一种用于管理多个 NodeJS 版本的工具。在日常工作中&#xff0c;我们可能同时在进行多个不同的项目开发&#xff0c;每个项目的需求不同&#xff0c;依赖与不同版本的NodeJS 运行环境。这种情况下&#xff0c;维护…

SAP PP学习笔记11 - PP中的MRP相关概念,参数,配置

上文讲了作业区的概念及配置。 SAP PP学习笔记08 - 作业区&#xff08;工作中心Work Center&#xff09;&#xff0c;作业区Customize-CSDN博客 SAP PP学习笔记09 - 作业区&#xff08;工作中心Work Center&#xff09;Customize2&#xff08;管理码&#xff0c;班次顺序&…

利用github pages建立Serverless个人博客

利用github pages建立Serverless个人博客 概述 使用github pages&#xff0c;可以在github上部署静态网站。利用这个功能&#xff0c;可以很方便地实现个人博客的发布托管。 比如我的个人博客&#xff1a;Buttering’s Blog 对应代码仓库&#xff1a;buttering/EasyBlog: 自…

【WPF学习笔记(一)】WPF应用程序的组成及Window类介绍

WPF应用程序的组成及Window类介绍 WPF应用程序的组成及Window类介绍前言正文1、WPF介绍1.1 什么是WPF1.2 WPF的特点1.3 WPF的控件分类 2、XAML介绍2.1 XAML的定义2.2 XAML的特点2.3 XAML的命名空间 3、WPF应用程序组成3.1 App.config3.2 App.xaml3.3 App.xaml.cs3.4 MainWindow…

微服务---feign调用服务

目录 Feign简介 Feign的作用 Feign的使用步骤 引入依赖 具体业务逻辑 配置日志 在其它服务中使用接口 接着上一篇博客&#xff0c;我们讲过了nacos的基础使用&#xff0c;知道它是注册服务用的&#xff0c;接下来我们我们思考如果一个服务需要调用另一个服务的接口信息&…

【C++】学习笔记——vector_3

文章目录 七、vector3. vector的模拟实现4. vector实现代码整合 未完待续 七、vector 3. vector的模拟实现 上篇文章我们讲解了非常 玄幻 的拷贝构造函数&#xff0c;同样的方法&#xff0c;我们也能用这种方法来实现 赋值重载函数 。 void swap(vector<T>& v) {s…

overflow:hidden对解决外边距塌陷的个人理解

外边距塌陷&#xff1a; 子元素的上外边距大于父元素的上外边距&#xff0c;导致边距折叠&#xff0c;取两者之间最大值&#xff0c;即子元素外边距&#xff0c;导致父元素上外边距失效。 解决办法&#xff1a;在父元素样式添加overflow:hidden;或者border:1px solid black;(不…

前端开发攻略---介绍HTML中的<dialog>标签,浏览器的原生弹框。

1、演示 2、介绍 <dialog> 标签用于定义对话框&#xff0c;即一个独立的窗口&#xff0c;通常用来显示对话框、提示框、确认框等弹出式内容。在对话框中&#xff0c;可以包含文本、表单元素、按钮等内容&#xff0c;用户可以和这些内容进行交互。 3、兼容性 4、示例代码 …

【C语言回顾】数据在内存中的存储

前言1. 概述2. 大小端字节序和字节序判断2.1 大端字节序&#xff08;Big-Endian&#xff09;2.2 小端字节序&#xff08;Little-Endian&#xff09;2.3 判断字节序的示例 3. 数据在内存中的存储3.1 整数在内存中的存储3.2 浮点数在内存中的存储 结语 ↓ 上期回顾: 【C语言回顾】…

【小菜鸟之---Ansible基础详解】

文章目录 1 【Ansible简介】1.1简介1.2 Ansible 特点1.3 Ansible的工作机制1.4Ansible任务工作模式 2【安装部署】2.1安装命令2.2 Ansible配置文件2.3主机清单配置2.4 基于ssh免密登录2.5常用命令 3【Ansible常用模块】3.1 ping模块3.2 shell模块3.3 command模块3.4 copy模块3.…

webox微信群发器多少钱?电脑微信群发软件哪个好用?微信群发助手一次能发多少人?最强稳定群发器来袭

今天给大家推荐一款我们目前在使用的电脑群发工具WeBox&#xff0c;不仅可以无限多开&#xff0c;方便你同时管理多个账号&#xff0c;群发功能更是十分强大&#xff0c;轻松释放你的双手。 软件下载地址>>密码&#xff1a;4as1 WeBox群发功能 下载WeBox打开登录&#x…

Golang | Leetcode Golang题解之第70题爬楼梯

题目&#xff1a; 题解&#xff1a; func climbStairs(n int) int {sqrt5 : math.Sqrt(5)pow1 : math.Pow((1sqrt5)/2, float64(n1))pow2 : math.Pow((1-sqrt5)/2, float64(n1))return int(math.Round((pow1 - pow2) / sqrt5)) }

《苍穹外卖》电商实战项目(java)知识点整理(P1~P65)【上】

史上最完整的《苍穹外卖》项目实操笔记&#xff0c;跟视频的每一P对应&#xff0c;全系列10万字&#xff0c;涵盖详细步骤与问题的解决方案。如果你操作到某一步卡壳&#xff0c;参考这篇&#xff0c;相信会带给你极大启发。 《苍穹外卖》项目实操笔记【中】&#xff1a;P66~P…

SiteServer 插件之 用户登录插件-用户注册

1、请确保已经安装了“用户登录插件”,如下图。 2、 显示管理->包含文件管理->include/header.html->编辑,如下图。 3、代码如下。 <header><div class="wrap"><div class="top-box clearfix"><div class="left-box…

cordova build android 下载gradle太慢

一、 在使用cordova run android / cordova build android 的时候 gradle在线下载 对于国内的链接地址下载太慢。 等待了很长时间之后还会报错。 默认第一次编译在线下载 gradle-7.6.1-all.zip 然后解压缩到 C:\Users\Administrator\.gradle 文件夹中,下载慢导致失败。 二…

前端工程化06-JavaScript模块化CommonJS规范ES Module

7、JavaScript模块化 在js开发中&#xff0c;他并没有拆分的概念&#xff0c;并不像java一样他可以拆分很多的包&#xff0c;很多的类&#xff0c;像搭积木一样完成一个大型项目的开发&#xff0c;所以js在前期的时候并不适合大型后端的项目开发&#xff0c;但是这些问题在后来…

CNN实现卫星图像分类(tensorflow)

使用的数据集卫星图像有两类&#xff0c;airplane和lake&#xff0c;每个类别样本量各700张&#xff0c;大小为256*256&#xff0c;RGB三通道彩色卫星影像。搭建深度卷积神经网络&#xff0c;实现卫星影像二分类。 数据链接百度网盘地址&#xff0c;提取码: cq47 1、查看tenso…

CentOS常用命令有哪些?

目录 一、CentOS常用命令有哪些&#xff1f; 二、不熟悉命令怎么办&#xff1f; 场景一&#xff1a;如果是文件操作&#xff0c;可以使用FileZilla工具来完成 场景二&#xff1a;安装CentOS桌面 一、CentOS常用命令有哪些&#xff1f; CentOS 系统中有许多常用命令及其用法…