Java-数据结构-链表-高频面试题(1)

news2025/1/8 8:01:53

在上一篇文章中,我们学习了链表中的"单向链表"但学可不代表就是学会了,能够运用链表的地方比比皆是,解题方法也是层出不穷,今天就让我们巩固一下"单向链表"的知识吧~

第一题:相交链表

📚 思路提示

想要判断链式结构中是否存在环,就要证明两者有相交,所以就要找到两者的相交点。而两个单链表的长度时常有所不同,所以遍历时就可能出现错过相交点的情况,因此我们最好想一个"特殊的起始点",即为"较长链表的中间结点,并且从该结点开始到最后的长度等于较短链表的长度"。

想要拿到这个结点并不难,我们只需要求出两个链表各自的长度,再求出链表长度的差值,这个差值为几,就让较长链表向后走几步,走完后较长链表剩下的部分链表长度,就正好等于较短链表的长度了~

图解

我们只需要注意,遍历完两个链表后仍然没有相交点即为"非相交链表"就好了~

📖 代码示例

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode curA = headA;
        int lena = 0;
        ListNode curB = headB;
        int lenb = 0;
        while(curA != null){
            curA = curA.next;
            lena++;
        }
        while(curB != null){
            curB = curB.next;
            lenb++;
        }
        int num = Math.max(lena,lenb) - Math.min(lena,lenb);
        if(lena > lenb){
            while(num-- > 0){
                headA = headA.next;
            }
        }else {
            while(num-- > 0){
                headB = headB.next;
            }
        }
        while(headA != headB){
            headA = headA.next;
            headB = headB.next;
            if(headA == null || headB == null){
                return null;
            }
        }
        return headA;
    }
}

第二题:反转链表

📚 思路提示想要做到指定区间内反转单链表,我们需要先学会如何整体反转单链表

关于单链表我们知道,结点只能一直向前走,不能往回走,而且想要两个结点互换位置,就代表至少需要两个结点来进行操作,而我们不仅仅想交换两个结点,交换后我们还需要让结点继续往后走,直到将需要的范围全部进行反转才可以。

而想要交换后再让结点继续往后走,只用两个结点就会出现问题了,比如上图中我们想将" 2 "和" 3 "的位置相互交换,所以交换后" 3 "的下一个就应该是" 2 ",而这样就会出现"丢失原链表"的问题,所以我们还需要第三个链表用来确保结点能够正常的后移~

(为防止访问越界,我们可以将第三个链表放入循环内部定义)

我们来演示一下整体反转单链表是何种过程。

图解

这就是整体反转单链表的全过程了,还是比较好理解的~

(记得把反转链表的末尾加上null)

📖 代码示例

class Solution {
    public ListNode reverseList(ListNode head) {
        if(head == null || head.next == null){
            return head;
        }
        ListNode cur = head;
        ListNode curn = head.next;
        while(curn != null){
            ListNode curN = curn.next;
            curn.next = cur;
            cur = curn;
            curn = curN;
        }
        head.next = null;
        return cur;
    }
}

第三题:反转链表 II

📚 思路提示:学会了链表的反转之后,我们来尝试一下这道题,本质上虽然还是反转链表,但是相比上一题,这一题需要考虑的就比较多了。

首先我们先设置一个计数器,从头开始遍历链表,每移动一个结点,计数器相应加一,直到计数器与left相等时,我们开始反转链表~并且在反转链表的途中,left还要时刻加一,直到计数器与right相等时,我们的反转链表工作结束~

(这里就不画图演示了,和上一题是一样的~)

还记得嘛?上一题虽然主要考察反转链表,但是就算会了反转链表,如果忘记了将 " head = null " 也还是没有用的~而 " head = null " 是因为将链表整体反转后,我们的尾结点就不再是之前的尾结点了,也正是因为如此,新的尾结点后面便不是 "null" ,而是" 原链表中第二个结点 ",这是因为 "新的尾结点是原来的头结点,原头结点的next就是第二个结点"~ 

上面就是一种情况,所代表的是"从头到尾全部反转",即"left == 1 && right == 链表长度"

(时间复杂度为O(n)的情况下无法拿到链表长度,所以我们可以通过"反转列表尾结点的next是否为null"来确认right是否为链表长度~)

而就这两种因素来说,能够组成四种不同的组合,这也就是解题的关键了。

图解

(以下对应操作中,lcur代表"反转链表首结点的前一结点"Lcur代表"反转链表首结点")

📕 (left == 1 && curn != null)

📖 对应操作head.next = curn;

📕 (left != 1 && curn == null)

📖 对应操作lcur.next = cur;

                        Lcur.next = null;

📕 (left != 1 && curn != null)

📖 对应操作lcur.next = cur;

                        Lcur.next = curn;

📕 (left == 1 && curn == null)

📖 对应操作Lcur.next = null;

📖 代码示例

class Solution {
    public ListNode reverseBetween(ListNode head, int m, int n) {
        if (head == null || head.next == null || m == n) {
            return head;
        }
        int left = 1;
        ListNode cur = head;
        // 用于保存反转链表首结点的前一结点
        ListNode lcur = null;
        // 用于保存反转链表首结点
        ListNode Lcur = null;
        while (cur != null && left != m) {
            //获取反转链表首结点的前一结点
            if (left == m - 1) {
                lcur = cur;
            }
            cur = cur.next;
            left++;
        }
        //获取反转链表首结点
        Lcur = cur;
        ListNode curn = cur.next;
        while (curn != null && left != n) {
            ListNode curN = curn.next;
            curn.next = cur;
            cur = curn;
            curn = curN;
            left++;
        }
        if(m == 1 && curn != null){
            head.next = curn;
        }else if(m != 1 && curn == null){
            lcur.next = cur;
            Lcur.next = null;
        }else if(m != 1 && curn != null){
            lcur.next = cur;
            Lcur.next = curn;
        }else{
            Lcur.next = null;
        }
        if(m != 1){
            return head;
        }
        return cur;
    }
}

第四题:链表的中间结点

📚 思路提示:这题要求的是中间结点,相信聪明的你稍微思考一下就有思路咯~对的,其实就是使用快慢指针我们只需要创建两个结点," slow = head " 和 " fast = head ",当 fast 走到末尾的时候,slow所处的位置就是中间结点了。

图解

📖 代码示例

class Solution {
    public ListNode middleNode(ListNode head) {
        if(head == null){
            return null;
        }
        ListNode slow = head;
        ListNode fast = head;
        while(fast != null && fast.next != null){
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }
}

第五题:链表的回文结构

📚 思路提示回文结构相信对大家来说都不陌生吧~这是一种" 向前和向后读都相同的序列 "。

或许大家心里想的已经很简单了吧~可能想着"全反转一遍,再和原来的链表对比一下~",其实并没有这么简单,我们注意,题目要求我们设计时间复杂度为O(n)的解题方法这也就代表我们必须只遍历一次链表就判断出它是否为"回文结构"。

虽然不能"全反转一遍",但反转在这题中确实是非常至关重要的一步~相信经过上两道题的练习,大家已经能够流畅的反转链表了~

因为回文结构 "从前和向后读都相同",也就代表了后半部分的反转链表应该等于前半部分的链表,所以此题中我们需要反转的部分是"链表的后半部分"~

而想要拿到后半部分,我们就要找到中间节点这也是我们的上一题呀~这回思路通畅了吧~看看图解吧!

图解

📕 寻找中间结点

📕 反转链表

这里大家就能看出一些问题了~没错,奇数个结点和偶数个结点的情况是不同的~所以我们对比的时候采用这这样的策略:

📕 最后对前后链表进行对比时,将最后一对结点拿出循环单独进行对比

📖 代码示例

class Solution {
    public boolean isPalindrome(ListNode head) {
        if(head == null || head.next == null){
            return true;
        }
        ListNode slow = head;
        ListNode fast = head;
        while(fast != null && fast.next != null){
            slow = slow.next;
            fast = fast.next.next;
        }
        //保存初始反转结点
        ListNode c = slow;
        ListNode cur = slow.next;
        while(cur != null){
            ListNode curn = cur.next;
            cur.next = slow;
            slow = cur;
            cur = curn;
        }
        //将新的尾结点next = null
        c.next = null;
        //保存反转后反转链表表头
        cur = slow;
        //slow.next != null 是将最后一对结点保留
        //用于后续对"奇数个结点"和"偶数个结点"进行区分
        while(slow.next != null){
            //值不相同则不是回文链表
            if(head.val != slow.val){
                return false;
            }
            slow = slow.next;
            head = head.next;
        }
        //head == cur 代表奇数个结点
        //head.val == slow.val 代表偶数个结点
        if(head == cur || head.val == slow.val){
            return true;
        }
        return false;
    }
}

第六题:环形链表

📚 思路提示:这题并不难,我们只需要创建一对快慢指针,在两者都不等于null的情况下一直循环,如果在某一刻两个结点相遇了,就代表这是一个环形链表~

图解

📖 代码示例

public class Solution {
    public boolean hasCycle(ListNode head) {
        if(head == null){
            return false;
        }
        ListNode slow = head;
        ListNode fast = head;
        while(fast != null && fast.next != null){
            slow = slow.next;
            fast = fast.next.next;
            if(slow == fast){
                return true;
            }
        }
        return false;
    }
}

第七题:环形链表 II

📚 思路提示:这题感觉属于是一个数学题...我们直接看图解:

图解

而最后得到的 X = Y 则代表"相遇点到入口的距离" = "头结点到入口的距离",这样一来问题也就迎刃而解了,我们只需要找到两个结点的相遇点,然后相遇点的结点与头结点同时一步一步的移动,直到两者相遇,这个结点就是入口结点~

📖 代码示例

public class Solution {
    public ListNode detectCycle(ListNode head) {
        if(head == null || head.next == null){
            return null;
        }
        ListNode slow = head.next;
        ListNode fast = head.next.next;
        while(slow != fast){
            if(fast == null || fast.next == null){
                return null;
            }
            slow = slow.next;
            fast = fast.next.next;
        }
        slow = head;
        while(slow != fast){
            slow = slow.next;
            fast = fast.next;
        }
        return slow;
    }
}

那么这次关于链表的练习题就为大家分享到这里啦,作者能力有限,如果有讲得不清晰或者不正确的地方,还请大家在评论区多多指出,我也会虚心学习的!那我们下次再见哦~

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

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

相关文章

5. 多线程(3) --- synchronized

文章目录 前言1. 如何解决线程安全问题 [回顾]2. synchronized 关键字2.1. 示例2.2.对示例进行变化2.3 synchronized的其他写法2.4 synchronized的特性2.4.1 互斥2.4.2. 刷新内存2.4.3. 可重入 前言 前面我们通过在两个线程中共同对count进行加一操作,最后得到的结…

阿尔法linux开发板ping不通百度

我使用的阿尔法linux板子,发现按照《03【正点原子】I.MX6U网络环境TFTP&NFS搭建手册V1.3.2》一套操作下来,还是没办法实现板子上网。 我总结了下面方法,我如何实现联网和互ping通,大致总结下三步 一、pc端的wifi网络&#xf…

Qt之屏幕录制设计(十六)

Qt开发 系列文章 - screencap(十六) 目录 前言 一、实现原理 二、实现方式 1.创建录屏窗口 2.录屏窗口类定义 3.自建容器对象定义 4.用户使用 5.效果演示 总结 前言 利用Qt实现屏幕录制设计,可以通过使用Qt自带的类QScreen、QPixma…

AI通过数据构建一个独有对话机器人

AI通过数据构建一个独有对话机器人,尝试构建快速构建专有知识的机器人。 前端使用tinker实现一个简单的对话窗口, 后端使用自己的数据进行不断的训练,有需要的可以依据自己的实际情况进行修改,和优化 import tkinter as tk fro…

xml格式化(1):使用python的xml库实现自闭合标签

前言 最近一段时间一直想要写一个urdf格式化插件。 至于为什么嘛,因为使用sw2urdf插件,导出的urdf,同一标签的内容,是跨行的,这就导致,内容比较乱,而且行数比较多。影响阅读。 因此&#xff…

【免费】2004-2019年各省规模以上工业企业RD经费面板数据

2004-2019年各省规模以上工业企业R&D经费面板数据 1、时间:2004-2019年 2、来源:国家统计局、统计年鉴 3、指标:行政区划代码、地区、年份、规模以上工业企业R&D经费(万元) 4、范围:31省 5、规模以上工企&#xff0c…

电路学习(一)之电阻

电阻在电路中具有限制电流、分流、分压等功能,是电路中必不可少的组成部分。 1.什么是电阻? 电阻是一种符合欧姆定律(R)、限制电流流动的线性元件。简单来说,电阻就是可以限制电流流过的电子器件,其主要功…

Facebook元宇宙项目中的智能合约应用:提升虚拟空间的自治能力

近年来,Facebook在元宇宙领域的探索引起了广泛关注。元宇宙是一个融合虚拟现实(VR)、增强现实(AR)和互联网的沉浸式数字空间。在这个过程中,智能合约技术被认为是提升虚拟空间自治能力的关键工具。通过自动…

SSR 【1】【nuxt安装】

文章目录 前言如何解决 前言 nuxt提供了nuxi脚手架工具&#xff0c;让开发者便捷生成nuxt模板项目。nuxt官网 npx nuxilatest init <project-name>但是几乎大部分的人在安的时候都会遇到这个问题 如何解决 在C:\Windows\System32\drivers\etc\hosts中增加如下解析记录…

mv指令详解

&#x1f3dd;️专栏&#xff1a;https://blog.csdn.net/2301_81831423/category_12872319.html &#x1f305;主页&#xff1a;猫咪-9527-CSDN博客 “欲穷千里目&#xff0c;更上一层楼。会当凌绝顶&#xff0c;一览众山小。” 目录 基本语法 主要功能 常用选项详解 1. …

【APP】5分钟上手基于BurpSuite的APP抓包

step 1 手机和电脑连上同一个wifi step 2 ipconfig -all查看电脑在WLAN下的IP 这里为10.0.23.80 step3 bp设置监听的端口和ip&#xff0c;ip设置为上一步看到的ip step4 bp导出证书 der后缀改为cer 传给手机 step5 在设置中搜索证书&#xff0c;按步骤安装证书 step6 在…

【工业场景】用YOLOv8实现工业安全帽识别

工业安全帽识别是一项重要的工作安全管理措施&#xff0c;旨在防止工作场所发生头部伤害事故。通过使用YOLOv8等深度学习模型&#xff0c;可以实时准确地检测出工人是否佩戴安全帽&#xff0c;及时发现违规行为&#xff0c;为工人提供更安全的工作环境。 使用YOLOv8实现工业安全…

51单片机——共阴数码管实验

数码管中有8位数字&#xff0c;从右往左分别为LED1、LED2、...、LED8&#xff0c;如下图所示 如何实现点亮单个数字&#xff0c;用下图中的ABC来实现 P2.2管脚控制A&#xff0c;P2.3管脚控制B&#xff0c;P2.4管脚控制C //定义数码管位选管脚 sbit LSAP2^2; sbit LSBP2^3; s…

云安全博客阅读(二)

2024-05-30 Cloudflare acquires BastionZero to extend Zero Trust access to IT infrastructure IT 基础设施的零信任 不同于应用安全&#xff0c;基础设置的安全的防护紧急程度更高&#xff0c;基础设施的安全防护没有统一的方案IT基础设施安全的场景多样&#xff0c;如se…

深入探讨 Android 中的 AlarmManager:定时任务调度及优化实践

引言 在 Android 开发中&#xff0c;AlarmManager 是一个非常重要的系统服务&#xff0c;用于设置定时任务或者周期性任务。无论是设置一个闹钟&#xff0c;还是定时进行数据同步&#xff0c;AlarmManager 都是不可或缺的工具之一。然而&#xff0c;随着 Android 系统的不断演…

SAP销售订单的计划行类别是什么?销售订单是如何传递需求给MRP的?

文章目录 一、销售订单计划行类别的参数二、销售订单的项目类别的配置VOV4三、计划行类别的配置VOV6四、对销售订单项目类别分配计划行类别VOV5五、自定义计划行类别 【SAP系统PP模块研究】 #SAP #SD #PP #计划 #需求传递 一、销售订单计划行类别的参数 销售订单主体包括Head…

英伟达 RTX 5090 显卡赋能医疗大模型:变革、挑战与展望

一、英伟达 RTX 5090 与 RTX 4090 技术参数对比 1.1 核心架构与制程工艺 在探讨英伟达 RTX 4090 与 RTX 5090 的差异时&#xff0c;核心架构与制程工艺无疑是最为关键的基础要素&#xff0c;它们从根本上决定了两款显卡的性能上限与应用潜力。 1.1.1 核心架构差异 RTX 4090…

Web渗透测试之XSS跨站脚本 原理 出现的原因 出现的位置 测试的方法 危害 防御手段 面试题 一篇文章给你说的明明白白

目录 XSS介绍的原理和说明 Cross Site Scripting 钓鱼 XSS攻击原理 XSS漏洞出现的原因&#xff1a; XSS产生的原因分析 XSS出现位置&#xff1a; XSS测试方法 XSS的危害 防御手段&#xff1a; 其它防御 面试题: 备注&#xff1a; XSS介绍的原理和说明 嵌入在客户…

【C++】字符串与字符数|组操作详解:strcpy 和 strcat 的使用与解析

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;一、字符串数组的基本操作&#x1f4af;二、strcpy 的用法详解1. strcpy 的功能与原型2. 使用示例与代码演示3. 注意事项4. 扩展&#xff1a;为什么不能直接用 &#xff1f…

玩机搞机基本常识-------列举安卓机型一些不常用的adb联机命令

前面分享过很多 常用的adb命令&#xff0c;今天分享一些不经常使用的adb指令。以作备用 1---查看当前手机所有app包名 adb shell pm list package 2--查看当前机型所有apk包安装位置 adb shell pm list package -f 3--- 清除指定应用程序数据【例如清除浏览器应用的数据】 …