对于链表,笔者在之前就已经有过几篇文章,详细的讲解了!感兴趣的各位老铁,请进入博主的主页进行查找!
https://blog.csdn.net/weixin_64308540/?type=blog
言归正传!对于链表,光学不做,也是白搭!所以,今日来几道小小的练习题顺顺手吧!!
温馨小提示一下:该题为数据结构的题目!必须画图!请大家思考时候也在画图!!
206. 反转链表
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
示例 1:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
示例 2:
输入:head = [1,2]
输出:[2,1]
示例 3:
输入:head = []
输出:[]
提示:
链表中节点的数目范围是 [0, 5000]
-5000 <= Node.val <= 5000
进阶:链表可以选用迭代或递归方式完成反转。你能否用两种方法解决这道题?
其实刚看见这个题目的时候,笔者也是一脸懵逼!但是,后来想了想,用头插法是一个不错的选择,相信各位老铁也可以想出来更好的办法的!
下面请看笔者用头插法书写的简单代码:
public ListNode reverseList( ListNode head){
if (head == null){
return null; //说明一个节点都没有
}
if (head.next == null){
return null;//说明只有一个节点
}
ListNode cur = head.next;//先记录头节点后的第一个节点
head.next =null;
while (cur !=null){
ListNode curNext =cur.next; //一定要有
//头插法,插入cur
cur.next =head;
head=cur;
cur=curNext;
}
return head;
}
876. 链表的中间结点
给定一个头结点为 head 的非空单链表,返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
示例 1:
输入:[1,2,3,4,5]
输出:此列表中的结点 3 (序列化形式:[3,4,5])
返回的结点值为 3 。 (测评系统对该结点序列化表述是 [3,4,5])。
注意,我们返回了一个 ListNode 类型的对象 ans,这样:
ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL.
示例 2:
输入:[1,2,3,4,5,6]
输出:此列表中的结点 4 (序列化形式:[4,5,6])
由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。
提示:
给定链表的结点数介于 1 和 100 之间。
显而易见,该题可以用快慢指针来进行!!需要保持的是:快到速度是慢的速度的2倍!!
思路:同时在head位置出发,fast的速度是slow的速度的2倍,路程相同,所以,在fast在终点的时候,slow在中间位置
分析:当节点为奇数的情况下:
fast与slow第一次往后走时:
再次往后走时候:
此时fast.next==null,再往后走的话就会进行空指针异常了!所以,这个算是一个判断的条件吧!
当节点为偶数的情况下:
fast与slow第一次往后走时:
再次往后走时候:
此时fast==null,再往后走的话就会进行空指针异常了!所以,这个算是第二个判断的条件吧!
因此,经过上述两个条件的分析,我们可以有:循环结束的情况:fast==null ||fast.next==null这两个条件!
经过分析,我们可以得出,fast !=null && fast.next !=null,而且这两个条件的先后顺序不能互换!!原因在于:当fast为null的时候,进行fast.next != null的判断会造成空指针异常!
因此,我们有着一下的代码:
public ListNode middleNode(ListNode head){
ListNode fast = head;
ListNode slow = head;
while (fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
}
return slow;//循环结束的时候,此时slow正好在中间节点!
}
或许有着老铁会有小小的疑惑!当只有一个节点的时候,是如何判断的??或许大家忘了:当只有一个节点的时候,此时,fast.next == null,则不会进入循环!!
链表中倒数第k个结点
知识点链表双指针
描述
输入一个链表,输出该链表中倒数第k个结点。
示例1
输入:
1,{1,2,3,4,5}
返回值:
{5}
若是我们要求:仅仅遍历一遍链表,就能得出出后的倒数第K个节点,不知道你会怎么思考??
思路:快慢指针!!
如下图所示:
此时fast为最后一个节点,slow为倒数第三个节点,slow距fast需要走2步!!因此,想要倒数第K个节点,我们可以:
先让fast走K-1步(或者fast先走K步,此时,判断条件就变为fast为null)
fast走完K-1步之后,fast和slow开始一步一步往后走
当fast.next为null 的时候,slow所指的位置就是倒数第K个节点!
经过上述的分析,我们有着一下的代码:
//链表的倒数第k个节点
public ListNode FindKthToTail(ListNode head,int k) {
//判断是否合法
if (k<=0 || head == null){
return null;
}
ListNode fast =head;
ListNode slow =head;
//让fast先走k-1步
while (k-1 !=0){
fast =fast.next;
if (fast == null){
return null;
}
k--;
}
//fast和slow一块一步一步走
while (fast.next != null){
fast =fast.next;
slow =slow.next;
}
return slow;
}
21. 合并两个有序链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例 1:
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
示例 2:
输入:l1 = [], l2 = []
输出:[]
示例 3:
输入:l1 = [], l2 = [0]
输出:[0]
提示:
两个链表的节点数目范围是 [0, 50]
-100 <= Node.val <= 100
l1 和 l2 均按 非递减顺序 排列
这个思路理解起来有点儿难度!表达不怎么样!笔者仅仅提供自己的思路吧!
这个解题便是笔者的思路!尴尬
下面请看代码:
//合并两个有序链表
public ListNode mergeTwoLists(ListNode head1 , ListNode head2){
ListNode newhead =new ListNode();//实列化一个虚拟的节点
ListNode tmp =newhead;
//当两个链表都有数据的时候
while (head1 !=null && head2 != null){
if (head1.val < head2.val){
tmp.next=head1;
head1=head1.next;
tmp=tmp.next;
}else {
tmp.next=head2;
head2=head2.next;
tmp=tmp.next;
}
}
//当只有一个链表有数据的时候
if (head1 !=null){
tmp.next =head1;
}
if (head2 !=null){
tmp.next =head2;
}
return newhead.next;
}
OR36 链表的回文结构
知识点链表栈
描述
对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。
给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。
测试样例:
1->2->2->1
返回:true
思路:对于该题目,我们可以先找到中间位置,然后再将中间位置往后的进行反转一下!!
因此,我们有着下列的简单代码:
public boolean chkPalindrome(ListNode head) {
if (head ==null){
return false;
}
if(head.next == null){
return false;
}
ListNode fast = head;
ListNode slow = head;
//找中间节点
while (fast !=null &&fast.next !=null){
fast=fast.next.next;
slow=slow.next;
}
//翻转
ListNode cur =slow.next; //代表当前需要反转的节点
while (cur !=null){
ListNode curNext =cur.next;
cur.next =slow;
slow=cur;
cur=curNext;
}
//一个从前往后,一个从后往前
while (slow !=head){
if (head.val !=slow.val){
return false;
}
//偶数的情况
if (head.next ==slow){
return true;
}
slow=slow.next;//从后往前
head=head.next;//从前往后
}
return true;
}
对于这个题目,想必最难的地方在于,找到中间节点之后的翻转!!这个感觉有点儿难度!其他的想必都是很简单的思路了吧!!
CM11 链表分割
描述
现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。
在这个题目中,我们需要注意的是,x可能是链表里面的数据,也可能不是链表里面的数据!另外:原链表的最后一个节点所存储的数据跟x比的大小??(需不需要置null问题)
在下列的讲述中,假设x的值为15!
情况1:假设初始状态下的链表数据为:(链表的最后一个节点所存储的数据小于x的值)
经过链表分割之后:
情况2:假设初始状态下的链表数据为:(链表的最后一个节点所存储的数据大于x的值)
经过链表分割之后:
那么,问题来了,怎么将小于x的节点放在前面,并且还能保证顺序不变呢??
思路:假设有2段,第一段放<x的节点,第2段放>=x的节点,最后再将这2段串起来,不就可以了蛮??
因此,对于该用列:
我们有着:
因此,在这个思路中,我们只需要维护各个节点的next便可以了!!
在这个过程中,用到的还是原来的节点,没有实列化新的节点,所以,还是链表本身的修改!!不要忘记,将最后一个节点置为null!!
下面请看笔者的简单代码:
public ListNode partition(ListNode head,int x){
ListNode bs=null;
ListNode be=null; //bs be用来记录<x的节点
ListNode as=null;
ListNode ae=null; //as ae用来记录>=x的节点
ListNode cur =head;//滴滴代跑
while (cur !=null){//循环的总条件
if(cur.val<x){
if (bs==null){ //判断是否为第一次插入
bs=cur;//确保bs在头节点保持不动
be=cur;
}else {
be.next=cur; //尾插法插入节点
be=be.next;
}
}else {
if (as==null){
as=cur;//确保as在头节点保持不动
ae=cur;
}else {
ae.next=cur;
ae=ae.next;
}
}
cur=cur.next; //在上述的if……else语句中,都用到了该语句,所以可以提取出来!
}
//有可能不会同时存在即大于又小于x的数据!(即一边倒的情况)
if (bs==null){
return as;
}
//没有经过上述的if(be==null)语句,就说明,第一段不为空
be.next=as; //此时第一段不为空
if (as!=null){
ae.next=null; //如果第二段不为空,则将最后的一个节点置为空
}
return bs;
}
经过该列题,我们可以看出来:与链表相关的练习题很难!很难,但是我们要想成功的做出来,一定要学会思考,学会画图解决!
160. 相交链表
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
图示两个链表在节点 c1 开始相交:
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
自定义评测:
评测系统 的输入如下(你设计的程序 不适用 此输入):
intersectVal - 相交的起始节点的值。如果不存在相交节点,这一值为 0
listA - 第一个链表
listB - 第二个链表
skipA - 在 listA 中(从头节点开始)跳到交叉节点的节点数
skipB - 在 listB 中(从头节点开始)跳到交叉节点的节点数
评测系统将根据这些输入创建链式数据结构,并将两个头节点 headA 和 headB 传递给你的程序。如果程序能够正确返回相交节点,那么你的解决方案将被 视作正确答案 。
示例 1:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,6,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
— 请注意相交节点的值不为 1,因为在链表 A 和链表 B 之中值为 1 的节点 (A 中第二个节点和 B 中第三个节点) 是不同的节点。换句话说,它们在内存中指向两个不同的位置,而链表 A 和链表 B 中值为 8 的节点 (A 中第三个节点,B 中第四个节点) 在内存中指向相同的位置。
示例 2:
输入:intersectVal = 2, listA = [1,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Intersected at '2'
解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [1,9,1,2,4],链表 B 为 [3,2,4]。
在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。
示例 3:
输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。
由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
这两个链表不相交,因此返回 null 。
提示:
listA 中节点数目为 m
listB 中节点数目为 n
1 <= m, n <= 3 * 104
1 <= Node.val <= 105
0 <= skipA <= m
0 <= skipB <= n
如果 listA 和 listB 没有交点,intersectVal 为 0
如果 listA 和 listB 有交 点,intersectVal == listA[skipA] == listB[skipB]
进阶:你能否设计一个时间复杂度 O(m + n) 、仅用 O(1) 内存的解决方案?
解题思路:先求出两个节点的长度,在球场两个节点的长度的差值(长度的差值体现在—》在节点相交之前的部分)若差值为0,则两个节点一块走,相遇的时候为相交的节点,若差值不为0,则长的节点先走差值个长度的步数,然后在一块走,相遇的时候为相交的节点!!
那么问题来了:
你知道哪个链表长吗??
你怎么判断相遇??是用headA==headB吗??但是,需要注意的是:当headA==headB==null的情况!走完了都不一定相交!
经过上述的分析,我们有着以下的代码:
public ListNode getIntersectionNode(ListNode headA , ListNode headB){
//分别求出两个链表的长度
int lenA=0;
int lenB=0;
ListNode pl=headA;//假设pl对应的为长链表
ListNode ps=headB;//假设ps对应的为短链表
while (pl !=null){
lenA++;
pl=pl.next;
}
while (ps !=null){
lenB++;
ps=ps.next;
}
//求出长度以后,此时pl和ps此时都指向null,所以要指回来
pl=headA;
ps=headB;
//通过len的正负,可以判断两个链表的长度
int len=lenA-lenB;
//根据len的正负,来修改指向
if (len<0){
pl=headB;//确保pl指向的为长链表
ps=headA;//确保ps指向的为短链表
len=lenB-lenA;
}
//此时,len一定是个正数!
//pl指向的为长链表 并且 ps指向的为短链表
//让长链表先走len步
while (len !=0){
pl=pl.next;
len--;
}//出while循环的时候,长链表与短链表距离相交的或者null有着相同的距离
while (pl != ps){
pl=pl.next;
ps=ps.next;
}//此时pl与ps要么相交,要么都为null
return pl;
}
思路很重要哟!!加油
141. 环形链表
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
提示:
链表中节点的数目范围是 [0, 104]
-105 <= Node.val <= 105
pos 为 -1 或者链表中的一个 有效索引 。
进阶:你能用 O(1)(即,常量)内存解决此问题吗?
根据上面的题目,我们可以看出来:最后一个节点,存储了前面某个数据的地址!!
对于该题的解题的思路为:快慢指针
快慢指针:即慢指针一次走一步,快指针一次走两步,两个指针从链表的起始位置开始进行,如果链表带环,则