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

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

第一题:相交链表

📚 思路提示

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

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

图解

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

📖 代码示例

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;
    }
}

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

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

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

相关文章

JVM实战—OOM的定位和解决

1.如何对系统的OOM异常进行监控和报警 (1)最佳的解决方案 最佳的OOM监控方案就是:建立一套监控平台,比如搭建Zabbix、Open-Falcon之类的监控平台。如果有监控平台,就可以接入系统异常的监控和报警,可以设置当系统出现OOM异常&…

照片做成图书小程序开发制作介绍

照片做成图书小程序系统,主要是让用户直接通过小程序选择需要做成书的类型和照片排版布局模板,以及上传照片的数量。照片上传完成后,生成模板图片样式进行预览或编辑修改。修改完成全部保存。保存后生成完整的照片书进行预览没问题&#xff0…

云商城--业务+架构学习和环境准备

云商城业务架构学习和环境准备 B2B:Business to Business,交易双方的身份都是商家,也就是商家将商品卖给商家,类似采购、批发类购物,国内代表性网站阿里巴巴批发网 C2C:Customer to Customer,…

Elasticsearch:Lucene 2024 年回顾

作者:来自 Elastic Chris Hegarty 2024 年对于 Apache Lucene 来说又是重要的一年。在本篇博文中,我们将探讨主要亮点。 Apache Lucene 在 2024 年表现出色,发布了许多版本,包括三年来的首次重大更新,其中包含令人兴奋…

基于LabVIEW的BeamGage自动化接口应用

设置 National Instruments LabVIEW可执行程序需要被配置为使用.NET 4框架。.NET允许自定义可执行程序的运行方式。可通过以下方式实现: 在LabVIEW安装目录中创建一个名为LabVIEW.exe.config的文本文件(例如:C:\Program Files\National Ins…

卸载干净 IDEA(图文讲解)

目录 1、卸载 IDEA 程序 2、注册表清理 3、残留清理 1、卸载 IDEA 程序 点击屏幕左下角 Windows 图标 -> 设置-控制面板->intellij idea 勾选第一栏 Delete IntelliJ IDEA 2022.2 caches and local history,表示同时删除 IDEA 本地缓存以及历史。 Delete I…

李宏毅机器学习课程笔记02 | 机器学习任务攻略General Guide

第一步:分析loss on training data 先检查在训练数据上模型是否很好的学习 情况1:如果在训练集上,loss很大,说明在训练资料上没有训练好 可能性1:设置的模型太简单了,模型存在model bias模型偏差&#x…

【C++】19.多态

文章目录 1. 多态的概念2. 多态的定义及实现2.1 多态的构成条件2.1.1 实现多态还有两个必须重要条件:2.1.2 虚函数 (Virtual Function)定义:特性:示例代码:代码分析1. 类定义部分2. 主函数部分运行结果 重点讲解1. 虚函数的作用2.…

光伏仿真与设计系统应用架构深度剖析

在光伏产业蓬勃发展的时代背景下,绿虫光伏仿真与设计系统成为推动其高效发展的核心力量。其应用架构涵盖多个关键步骤,每个环节都紧密相扣,共同构建起精准且高效的设计体系。 气象分析作为开篇之笔,起着基石般的重要作用。系统全…

进程间通讯

简介: 进程间通讯方式有: 1.内存映射(mmap): 使用mmap函数将磁盘空间映射到内存 2.管道 3.信号 4.套接字(socket) 5.信号机制 通过进程中kill函数,去给另一个函数发送信号&a…

空压机接入配置实例:利用 MODBUS - TCP 转 Ethernet IP 网关实现连接

在工业自动化生产环境中,空压机作为重要的气源设备,其稳定运行和有效监控对于整个生产流程至关重要。然而,不同厂家生产的空压机可能采用不同的通信协议,这给集中监控和管理带来了挑战。在本次案例中,我们遇到的空压机…

基于 Boost.Asio 和 Boost.Beast 的异步 HTTP 服务器(学习记录)

已完成功能: 支持 GET 和 POST 请求的路由与回调处理。 解析URL请求。 单例模式 管理核心业务逻辑。 异步 I/O 技术和 定时器 控制超时。 通过回调函数注册机制,可以灵活地为不同的 URL 路由注册处理函数。 1. 项目背景 1.1 项目简介 本项目是一个基于…

Harmony开发【笔记1】报错解决(字段名写错了。。)

在利用axios从网络接收请求时,发现返回obj的code为“-1”,非常不解,利用console.log测试,更加不解,可知抛出错误是 “ E 其他错误: userName required”。但是我在测试时,它并没有体现为空,…

Spring源码分析之事件机制——观察者模式(二)

目录 获取监听器的入口方法 实际检索监听器的核心方法 监听器类型检查方法 监听器的注册过程 监听器的存储结构 过程总结 Spring源码分析之事件机制——观察者模式(一)-CSDN博客 Spring源码分析之事件机制——观察者模式(二&#xff…

关于Mac中的shell

1 MacOS中的shell 介绍: 在 macOS 系统中,Shell 是命令行与系统交互的工具,用于执行命令、运行脚本和管理系统。macOS 提供了多种 Shell,主要包括 bash 和 zsh。在 macOS Catalina(10.15)之前&#xff0c…

【C++】20.二叉搜索树

文章目录 1. 二叉搜索树的概念2. 二叉搜索树的性能分析3. 二叉搜索树的插入4. 二叉搜索树的查找5. 二叉搜索树的删除6. 二叉搜索树的实现代码7. 二叉搜索树key和key/value使用场景7.1 key搜索场景:7.2 key/value搜索场景:7.3 主要区别:7.4 ke…

【大模型+本地自建知识图谱/GraphRAG/neo4j/ollama+Qwen千问(或llama3)】 python实战(中)

一、建立基本的知识图谱并导入neo4j 这里我举例用的属性表、关系表,大概格式如下 id名字颜色a1苹果红色 startrelenda1属于b1 启动neo4j(关于neo4j的安装此处不再赘述) import pandas as pd from py2neo import Graph, Node, Relationship…

【pyqt】(四)Designer布局

布局 之前我们利用鼠标拖动的控件的时候,发现一些部件很难完成对齐这些工作,pyqt为我们提供的多种布局功能不仅可以让排版更加美观,还能够让界面自适应窗口大小的变化,使得布局美观合理。最常使用的三种布局就是垂直河子布局、水…

解决“KEIL5软件模拟仿真无法打印浮点数”之问题

在没有外部硬件支持时,我们会使用KEIL5软件模拟仿真,这是是仿真必须要掌握的技巧。 1、点击“Project”,然后点击“Options for target 项目名字”,点击“Device”,选择CPU型号。 2、点击“OK” 3、点击“Target”,勾选“Use Mi…

【项目实战1】五子棋游戏

目录 C语言编程实现五子棋&#xff1a;&#xff1a; game.h game.c 1.打印菜单 2.打印棋盘 3.玩家下棋 4.判断五子连珠 5.判断输赢 6.游戏运行 game.c完整源代码展示 test.c C语言编程实现五子棋&#xff1a;&#xff1a; game.h #pragma once #include<stdio.h> …