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

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

        给定一个整数数组 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.rhkb.cn/news/320034.html

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

相关文章

微信小程序之搜索框样式(带源码)

一、效果图&#xff1a; 点击搜索框&#xff0c;“请输入搜索内容消失”&#xff0c;可输入关键字 二、代码&#xff1a; 2.1、WXML代码&#xff1a; <!--搜索框部分--><view class"search"><view class"search-btn">&#x1f50d;&l…

QT5之事件——包含提升控件

事件概述 信号就是事件的一种&#xff0c;事件由用户触发&#xff1b; 鼠标点击窗口&#xff0c;也可以检测到事件&#xff1b;产生事件后&#xff0c;传给事件处理&#xff0c;判断事件类型&#xff0c;后执行事件相应函数&#xff1b; 类似单片机的中断&#xff08;中断向量…

Docker 入门与实践:从零开始构建容器化应用环境

Docker 一、docker常用命令docker ps 格式化输出Linux设置命令别名 二、数据卷相关命令挂载到默认目录&#xff08;/var/lib/docker&#xff09;挂载到本地目录 三、自定义镜像Dockerfile构建镜像的命令 四、网络自定义网络 五、DockerCompose相关命令 一、docker常用命令 dock…

Superset二次开发之Legend功能优化

背景 Legend数据太长,影响整体图表体验,为改善用户体验,需要实现:1.数据省略展示,‘...’表示,鼠标悬停时,展示完整信息 2:文本内容从左向右滚动展示 柱状图优化 柱状图来自第三方Echarts插件,效果展示 功能核心在于红框的内容 option = {tooltip: {trigger: item,ax…

软件杯 深度学习的水果识别 opencv python

文章目录 0 前言2 开发简介3 识别原理3.1 传统图像识别原理3.2 深度学习水果识别 4 数据集5 部分关键代码5.1 处理训练集的数据结构5.2 模型网络结构5.3 训练模型 6 识别效果7 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习…

Vue3+Nuxt3 从0到1搭建官网项目(SEO搜索、中英文切换、图片懒加载)

Vue2Nuxt2 从 0 到1 搭建官网~ Vue3Nuxt3 从0到1搭建官网项目 安装 Nuxt3&#xff0c;创建项目初始化的 package.json项目结构初始化项目pages 文件下创建index.vue引入sass修改 app.vue 文件查看效果 配置公共的css、metaassets下的cssreset.scss 重置文件common.scss 配置nux…

CTF-WEB(MISC)

安全攻防知识——CTF之MISC - 知乎 CTF之MISC杂项从入门到放弃_ctf杂项 你的名字-CSDN博客 CTF MICS笔记总结_archpr 掩码攻击-CSDN博客 一、图片隐写 CTF杂项---文件类型识别、分离、合并、隐写_ctf图片分离-CSDN博客 EXIF&#xff08;Exchangeable Image File&#xff09;是…

考虑极端天气线路脆弱性的配电网分布式电源和储能优化配置模型

1 主要内容 程序主要参考《考虑极端天气线路脆弱性的配电网分布式电源配置优化模型-马宇帆》&#xff0c;针对极端天气严重威胁配电网安全稳定运行的问题。基于微气象、微地形对配电网的线路脆弱性进行分析&#xff0c;然后进行分布式电源接入位置与极端天气的关联性分析&…

​【收录 Hello 算法】第 3 章 数据结构

第 3 章 数据结构 Abstract 数据结构如同一副稳固而多样的框架。 它为数据的有序组织提供了蓝图&#xff0c;算法得以在此基础上生动起来。 本章内容 3.1 数据结构分类3.2 基本数据类型3.3 数字编码 *3.4 字符编码 *3.5 小结

Java | Leetcode Java题解之第59题螺旋矩阵II

题目&#xff1a; 题解&#xff1a; class Solution {public int[][] generateMatrix(int n) {int num 1;int[][] matrix new int[n][n];int left 0, right n - 1, top 0, bottom n - 1;while (left < right && top < bottom) {for (int column left; co…

uniapp:K线图,支持H5,APP

使用KLineChart完成K线图制作,完成效果: 1、安装KLineChart npm install klinecharts2、页面中使用 <template><view class="index"><!-- 上方选项卡 --><view class="kline-tabs"><view :style="{color: current==ite…

《动手学深度学习(Pytorch版)》Task03:线性神经网络——4.29打卡

《动手学深度学习&#xff08;Pytorch版&#xff09;》Task03&#xff1a;线性神经网络 线性回归基本元素线性模型损失函数随机梯度下降 正态分布与平方损失 线性回归的从零开始实现读取数据集初始化模型参数定义模型定义损失函数定义优化算法训练 线性回归的简洁实现读取数据集…

【c++】Resharper 去掉中文注释拼写

参考大神&#xff1a; Resharper 去掉注释拼写 reshaper的中文注释一堆下划线&#xff0c;看的很累、很乱&#xff1a; options 里 在code inspetion里 搜索 去掉 Typo in comment 就可以不在中文注释提示 重启vs reshaperd 中文注释下划线没了。小番茄的还在。

jsPDF + html2canvas + Vue3 + ts项目内,分页导出当前页面为PDF、A 页面内导出 B 页面的内容为PDF,隐藏导出按钮等多余元素

jsPDF html2canvas Vue3 ts Arco Design项目&#xff0c;分页导出当前页面为PDF、A 页面内导出 B 页面的内容为PDF&#xff0c;隐藏导出按钮等多余元素… 1.下载所需依赖 pnpm install --save html2canvaspnpm install --save jspdf引入依赖 <script setup lang"…

2010NOIP普及组真题 3. 导弹拦截

线上OJ&#xff1a; 一本通&#xff1a;http://ybt.ssoier.cn:8088/problem_show.php?pid1951 核心思想&#xff1a; 1、我们把导弹分为区间1和区间2来看。1#拦截区间1&#xff0c;2#拦截区间2。 2、则&#xff1a;1#的拦截半径为区间1 中 最远的导弹&#xff0c;而2#的拦截半…

关于 c++的模板库中的数组模板 is_array_v的测试

&#xff08;1&#xff09;该模板的源代码如下&#xff1a; template <class> // determine whether type argument is an array bool is_array_v false;template <class _Ty, size_t _Nx> bool is_array_v<_Ty[_Nx]> true;template <class _Ty>…

学习java中的interface接口

1.了解接口 java提供了一个关键字interface&#xff0c;用这个关键字我们可以定义出一个特殊的结构&#xff1a;接口 格式&#xff1a; public interface 接口名{ //成员变量&#xff08;常量&#xff09; //成员方法&#xff08;抽象方法&#xff09; } 注意&#xff1a;接…

Python——Fastapi管理平台(打包+优化)

目录 一、配置多个表 1、后端项目改造 2、导包报错——需要修改&#xff08;2个地方&#xff09; 3、启动后端&#xff08;查看是否有问题&#xff09; 4、配置前端 二、打包——成exe文件&#xff08;不包含static文件&#xff09;简单 1、后端修改 2、前端修改 3、运行打包命…

[论文笔记]Longformer: The Long-Document Transformer

引言 今天带来论文Longformer: The Long-Document Transformer的笔记。 基于Transformer的模型由于其自注意力操作而无法处理长序列&#xff0c;该操作随着序列长度呈二次扩展。为了解决这一限制&#xff0c;本篇工作提出了Longformer&#xff0c;其注意力机制随着序列长度呈…

批量邮箱API发送邮件的方法?如何使用API?

批量邮箱API发送邮件效率怎么样&#xff1f;API接口发信的优势&#xff1f; 批量发送邮件已经成为许多企业、机构或个人进行营销推广、信息传递的重要手段。然而&#xff0c;如何高效、准确地实现批量邮箱发送&#xff0c;却是许多人面临的难题。AokSend就来探讨一下利用API进…