前言
上一篇讲解了链表的基本操作详解,接下来练习一下链表的应用。
目录
1.删除链表中等于给定值 val 的所有节点。
移除链表元素习题链接: 移除链表元素
题解思路
实现在链表中删除所有给定的key
1.首先先遍历链表
2.如果有某个节点的val值等于vag,那么让这个节点的上一个节点next存放该节点的next(相当于就是跳过了那个相等的节点,跳过了相当于就是删除了)
3.然后继续遍历找下一个相同的,找到进行同样的操作。
4.最后解决头部问题,对链表的头的val与val进行比较,如果同则让头指向下一个节点即可。
代码实现
public ListNode removeElements(ListNode head, int val) { //看看是不是空的 if(head == null){ return null; } //定义两个节点指针,指向头另一个指向头的下一个 ListNode pevr = head; ListNode cur = head.next; while(cur != null){ //2.如果有某个节点的val值等于vag,那么让这个节点的上一个节点next存放该节点的next // (相当于就是跳过了那个相等的节点,跳过了相当于就是删除了) if(cur.val == val){ pevr.next = cur.next; cur = cur.next; }else{ pevr = cur; cur = cur.next; } } //最后解决头部问题,对链表的头的val与val进行比较,如果同则让头指向下一个节点即可。 if(head.val == val){ head = head.next; } return head; }
2.反转一个单链表
反转链表链接:反转链表
思路分析
反转一个链表是需要改变其链表结构的,
翻转链表,就是让第二个节点开始,依次进行头插
要做到这一步,需要设置两个指针,一个指向头节点的下一个节点
然后另一个指针就是保留这个节点的下一个节点,让第一个指针节点进行头插后,
再回来还能找到下一个要插入的节点地址,然后头结点变成插入好的节点,就这样遍历一下链表即可反转完成。
步骤
1.遍历链表
2.进行头插,改变头结点
3.继续下一个节点的头插,重复遍历完即可。
画图分析
反转前
反转后
代码实现
//翻转链表,就是让第二个节点开始,依次进行头插 public ListNode overturn(){ if (head ==null){ return null; } //如果是一个结点直接 if (head.next ==null){ return head; } //改变链表内部结构 ListNode cur = head.next; head.next = null; while(cur != null){ //1.循环条件咋么设置,还是cur != null,因为最后一个是null // 头插能实现第一个,但是后面的2接上呢,让cur返回来就行了 ListNode curNode = cur.next; cur.next = head; head = cur; cur = curNode;//当做指针 } return head; }
分析
ListNode curNode = cur.next; cur.next = head; head = cur; cur = curNode;//当做指针
3.链表的中间结点
给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结 点。
习题链接:链表中间结点
思路分析
利用两个指针,一个是慢一个快,快的是一次走两步
原理就是路程相同为800米的田径场跑道,A同学和B同学同时起跑,A同学是B同学的速度的2倍,那么当A到达终点,B肯定在中点。
1.创建两个指针同时指向头结点;
2.然后遍历一遍,注意循环结束的条件是判断快指针是否到达终点;
3.走完一遍,返回慢指针即是中间结点。
画图分析
第一步
第二步
第三步
此时当fast到达末尾,那么slow必然在中间位置。
代码实现
//快慢,速度为2倍,同样的起点,终点一样,速度是2倍 public ListNode middleNode(ListNode head){ ListNode fast = head; ListNode slow = head; while( fast != null && fast.next !=null )//fast两倍速度会提前一半到终点 //奇数情况fast.next ==null,偶数情况fast == null //这里需要注意,这两个条件不能换过来。 { fast = fast.next.next;//2倍速度走两步 slow = slow.next;//慢的走一步 } //走完说明slow在中间 return slow; }
4.链表中倒数最后k个结点
习题链接:链表中倒数最后k个结点_牛客题霸_牛客网 (nowcoder.com)
题目描述:输入一个长度为 n 的链表,设链表中的元素的值为 ai ,返回该链表中倒数第k个节点。
如果该链表长度小于k,请返回一个长度为 0 的链表。
第一解决办法
比如要返回倒数第2个结点,那么直接用链表的 总长度 - 2,得到的数就是头结点要走的长度。
走完,返回fast即可。
代码实现
ListNode fast = head; int count = 0; while(fast != null){ count++; fast = fast.next; } //限制一下k范围 if(head == null || k < 0 || k >count){ return null; } fast = head; //重置为头位置 int len = count - k; // while(len > 0){ fast = fast.next; len--; } return fast;
第二种解决办法
思路分析
输入一个链表,输出该链表中倒数第k个结点。 1.fast 先走k-1步 2.然后slow和fast同时走 3.fast到最后slow的位置就是倒数k-1 需要注意的是要对k进行判断是否合法 还需要解决当链表为空或者fast指向了空的情况原理fast和slow永远相差k-1步
画图分析
代码实现
//限制k值和防止空表 if ( k <= 0 || head == null ) { return null; } ListNode fast = head; ListNode slow = head; //怎么先让fast先走k-1步 for (int i = 0 ; i < k - 1 ; i ++) { fast = fast.next;//如果k>size()那么fast就指向null了 if (fast == null) { //处理k太大问题 return null; } } //同时走 //奇偶情况是如何的呢,通过画图分析是一样的 while (fast.next != null) { fast = fast.next; slow = slow.next; } //走完了然后勒 //走完就说明slow就是要求的位置 System.out.println(slow.val); return slow;
5.合并两个有序链表
习题链接:21. 合并两个有序链表 - 力扣(LeetCode)
题目描述:将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
思路分析
合并两个链表,要改变其结构
1.创建好一个新的链表的头结点,相当于一辆火车先有车头
2.然后让两个链表的头结点分别进行比较大小,小的先在新的链表里面进行尾插,然后头结点更换到下一个,然后继续比较大小。
3.最后比较完成,拼接完成新链表,返回新链表的头结点即可。
画图分析
代码实现
public ListNode mergeTwoLists(ListNode head1, ListNode head2) { //1.定义虚拟的字节,作为新链表头结点 ListNode newH = new ListNode(-1); ListNode tmpH = newH; //2.遍历进行比较大小 while(head1 != null && head2 != null){ if(head1.val < head2.val){ tmpH.next = head1; //进行尾插到新的链表中 head1 = head1.next; //头结点改变下一个。 }else{ tmpH.next = head2; head2 = head2.next; } tmpH = tmpH.next;//指向末尾 } //可能两个链表长度不一样,出了循环进行检查 if(head1 != null){ tmpH.next = head1; } if(head2 != null){ tmpH.next = head2; } //3.返回新链表头结点 return newH.next; }
6. 链表分割
习题链接:链表分割_牛客题霸_牛客网 (nowcoder.com)
题目描述:现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。
思路分析
链表的分割,要分割的话就要分两个区间,一个存放小于x的区间,另一个则是大于x的。
1.那么就有先创建四个指针分别为第一第二区间链表的头尾指针。
2.然后对链表进行一次遍历,每个结点的值依次与x比较,然后放到对应的区间。
3.遍历完,然后进行连接成一个新的链表,再返回新链表的头结点。
需要注意的是两种情况,如果第二区间是空的则无需连接
还有就是如果第一个区间为空,那么直接返回第二区间的头结点
最后就是要手动给第二区间的最后一个结点的next置为null不然会报错异常。
画图分析
第一步,第一个结点12小于35,因此放在第一个区间be、bs这时都指向第一个结点
第二步,第二个结点值是5也小于35,放第一个区间,然后尾指针be指向他。
第三步,第三个结点值大于35,放到第二区间,后续遍历也如此,这里就不一一列出。
最后结果
代码实现
//会改变链表的结构 public ListNode partition(ListNode head, int x) { // write code here ListNode cur = head; ListNode bs = null;//创建好节点类型的用于保存分割后两个区间的节点位置地址 ListNode be = null; ListNode as = null; ListNode ae = null; while(cur != null){//遍历 //分割 if(cur.val < x ){ if(bs == null){//如果是第一个头尾都指向他 bs = cur; be = cur; }else{ //如果是第二个以后进行尾插 be.next = cur;//尾插原理 be = be.next; } }else{ if(as == null){ as = cur; ae = cur; }else{ ae.next = cur; ae = ae.next; } } cur = cur.next; } //有一种情况,如果链表是3333,x为3,那么前面区间是空的 //此时be.next会空指针异常 //因此要对第一区间进行判断 if(be == null){ return as;//如果第一区间是空那么没必要连接,返回第二区间的头即可。 } //连接起来 //用第一个区间的尾和第二个区间的头进行连接 be.next = as; //还有一个情况,就是拼接起来后,第二区间的尾节点的next有可能不为空,异常 if(as != null ){//此时需要手动进行置空 //第二区间没有数据情况 ae.next = null; } return bs;//返回第一区间的头 }
7.链表回文
习题链接:链表的回文结构_牛客题霸_牛客网 (nowcoder.com)
题目描述:
对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。
给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。
思路分析
要判断是否是回文,那么就对前后两边进行比较,如果相同两边同时走一步,如果不是同则返回false,然后继续判断,直到结束,能走到结束说明是回文结构。
那么要实现上面的效果,首先就得需要改变链表后半部分的结构。
1.首先要用快慢指针找到中间位置,然后翻转中间后面节点(使用上面的翻转思路)。
2.翻转完成,此时slow处于链表末尾,让头head和尾slow同时往中间走,每走一步进行比较val内容是否相同,然后继续下一步,直到循环结束。
3.走完返回true。
需要注意的是需要区分奇数个节点和偶数个节点的区别就行。
画图分析
1.快慢指针取中间
2.翻转后面节点
3.前后往中走
4.解决偶数问题
代码实现
public boolean chkPalindrome(ListNode head) { if(head == null || head.next == null){ return true; } // write code here //中间位置 ListNode fast = head; ListNode slow = head; while(fast != null && fast.next != null ){//这个顺序不能改啊,因为会出现空指针异常 fast = fast.next.next;//走两步 slow = slow.next; } //走完即可得到中间位置 //从中间开始翻转 //定义两个指向slow的后面,把slow当做头,然后进行翻转 ListNode cur = slow.next; while(cur != null){ ListNode curNext = cur.next;//提前存好cur的下一个地址,不然丢失 cur.next = slow;//slow后面指向存放slow地址 slow = cur; //然后slow相当于头结点往后走 cur = curNext;//然后继续下一个节点翻转 } //走到这里翻转完成,此时slow指向最后一个节点位置 //3.进行前后移动 //fast = head; //让fast回到开头 //此时fast和slow一个在头一个在尾,进行移动即可。 //可是怎么知道走到中间了呢 while(head != slow){ //因为后面被翻转了,所以中间的左右节点的next放的地址都是中间的地址。 if(head.val != slow.val){//进行值的对比,如果有一个不对就不是回文 return false; } //解决偶数的情况 if(head.next == slow){ return true; } //这三步骤得按顺序来 head = head.next; slow = slow.next; } return true; }
8. 相交链表
题目描述:给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null
。
思路分析
1.分别求长度
2.求两个长度差值,让长的先走完差值步数
3.然后同时走
4.当他们相遇的时候那个相遇点就是相交节点
画图分析
代码实现
public ListNode getIntersectionNode(ListNode headA, ListNode headB) { // 忽略了一点,这俩链表不一定都是在第n个节点相交,所以要让长度长的提前走几步 int len1 = getLen(headA); int len2 = getLen(headB); // 俩指针分别指向俩链表,如果某一个指针到达尾部还没有匹配到相交点,则无交点 ListNode fast = headA; ListNode slow = headB; //让长的走差值步 if (len1 > len2) { for (int i = 0; i < len1 - len2; i++) { fast = fast.next; } } else if (len2 > len1) { for (int i = 0; i < len2 - len1; i++) { slow = slow.next; } } //“同步进度之后同时走再找交点” while (fast != null && slow != null) { if (fast == slow) return fast; fast = fast.next; slow = slow.next; } return null; } int getLen(ListNode head) { ListNode cur = new ListNode(); cur.next = head; int res = 0; while (cur.next != null) { res++; cur = cur.next; } return res; }
9.环形链表1
题目描述:
给你一个链表的头节点 head
,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos
不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true
。 否则,返回 false
。
思路分析
原理可以理解为追击问题
一个田径场圈,两人速度相差一倍,一定会有相遇的时候,相遇就是有环。
1.利用快慢指针,一个走两步,一个走一步
2.如果有环,那么两者肯定会相遇,每走完一步就进行判断
画图分析
代码实现
public boolean hasCycle(ListNode head) { ListNode fast = head; ListNode slow = head; while(fast != null && fast.next != null){ fast = fast.next.next; //走两步 slow = slow.next; if(fast == slow){//每走完一步就进行判断 return true; } } return false; }
10.环形链表2
题目链接:142. 环形链表 II - 力扣(LeetCode)
题目描述:
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 NULL
不允许修改 链表。
思路分析
找入环点。
让一个指针从链表起始位置开始遍历链表,同时让一个指针从判环时相遇点的位置开始绕环运行,两个指针 都是每次均走一步,最终肯定会在入口点的位置相遇。
画图分析
代码实现
public ListNode detectCycle(ListNode head) { ListNode fast = head; ListNode slow = head; while(fast != null && fast.next != null){ fast = fast.next.next; //走两步 slow = slow.next; if(fast == slow){//每走完一步就进行判断 break; } } if(fast ==null || fast.next == null){ return null; } //直接让慢指针从头开始走 //让fast在相遇点那里走,再次相遇就是入口点。 slow = head; while(slow != fast){ fast = fast.next; slow = slow.next; } return slow; }
总结
本篇主要对链表的习题进行讲解,如果有疑惑的或者讲解的地方有问题,可以在评论区交流谢谢。