24. 两两交换链表中的节点
先看的卡哥视频再自己动手做的,自己做跟听还是不一样的,编程学习是需要动手的! ᕦ(・ㅂ・)ᕤ
遇到的问题:
没搞清第一个临时节点和第二个临时节点的赋值时间
对临时节点temp0和temp1的理解:
必须一开始在循环中就把temp0和temp1赋值,然后才开始移动p,才能保证后续临时的temp节点是有效的!不信自己试试o(´^`)o
第一次写的代码:(先移动p.next之后再赋值temp1)
后面正确的代码:
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode newHead = new ListNode(-1, head); // 虚构一个头节点
ListNode p = newHead; // p相当于cur
ListNode temp0 = null;
ListNode temp1 = null;
while(p.next != null && p.next.next != null){ // 第一个判断的是偶数个节点的循环终止条件(当然也包括head节点为空时,0就是偶数),第二个判断是奇数个节点的,显然p.next得先判断,不然会空指针异常
// 显然先想到p.next要指向p.next.next,但是这样,有个问题,p.next原本的指向将会丢失(示例图1中的1),所以,先需要一个临时变量temp0来存储p.next
// 然后需要让p.next.next指向p.next,但是!!注意!!,不能先修改p.next再来保存p.next.next,不信自己试试o(´^`)o
// 如果先修改了p.next,p.next.next不就也跟着修改不是原来的值(3)了嘛
temp0 = p.next;
temp1 = p.next.next.next;
p.next = p.next.next;
p.next.next = temp0;
temp0.next = temp1;
// 别忘了移动p!!
p = p.next.next; // 移动两格就可以,接着循环操作后面的两个(p在示例图1中2位置(当然此时已经是1了,不用管)下次操作3和4)
}
return newHead.next;
}
}
19.删除链表的倒数第N个节点
这道题自己写了两种解法,一种是遍历了两次的方法,一种是自己写的双指针法。这两种方法我都用到了for循环来移动指针。
方法一:我先while循环得到链表长度,然后for循环找到倒数第n个节点的前驱。
方法二:我想象的是,每次都移动fast指针n个位置(这里会用到for循环),如果移动了n个位置之后,fast等于null了,说明原本fast的位置,就是我们要找到倒数第n个节点,然后因为slow指向的就是fast移动之前的位置,所以修改slow的next就完成了删除
但是在用for循环处理指针移动位置时,对指针所指位置有点懵了。
遇到的问题:
利用for循环结合i下标来移动指针时,对指针位置把控不准确
先上我方法一的代码吧,之后再来解释这个问题:
1.两次遍历法(单指针法)
代码如下:
public ListNode removeNthFromEnd(ListNode head, int n){
// 1.单指针法(哈哈,其实是遍历了两次,一次计算长度,一次循环找到倒数第n个节点前一个节点)
int listLength = 0;
ListNode newHead = new ListNode(-1,head);
ListNode p = newHead;
while(p.next != null){ // 计算链表的长度(不包括虚拟头结点)
listLength++;
p = p.next;
}
p = newHead; // 重新回到虚拟节点处,准备查找倒数第n个节点的位置
for(int i = 0; i< listLength - n; i++){// 找到第n个节点的前驱(记住是用i<)
p = p.next;
}
p.next = p.next.next;
return newHead.next;
}
在这个方法的中,如何让p移动到倒数第n个位置之前?(或者说,for循环结束之后,p真的是在倒数第n个位置之前吗?)这该怎么看呢?下面,通过一张图来辅助理解一下:
看完这张图,这下理解了:for循环的循环条件,就是步数。如果是<,那么就是n步;如果是<=,那么就是n+1步
而在我的方法一中,是想要找到倒数第n个节点的前一个节点位置,那么循环条件应该设置为lengthList - n(例如,上图中,lengthList=3,n=2,那么pred移动3-2=1步之后到达n=1这个位置,正好是倒数第2个节点的前一个节点位置)。
总之,利用for循环解决指针移动问题,关键在于3点:
1.p指针初始位置在哪?
2.根据循环条件判断移动步数
3.画图理解
2.双指针法(自己实现的,个人认为更好理解)
代码如下:
public ListNode removeNthFromEnd(ListNode head, int n){
// 2.双指针法(一次遍历)
ListNode newHead = new ListNode(-1,head);
ListNode fast = newHead.next;
ListNode slow = newHead;
while(fast != null){ // 当fast这个指针为空了,说明已经找到倒数第n个节点了,此时的slow就是它的前驱
for(int i =0; i < n; i++){ // 移动n次fast
fast = fast.next;
}
if(fast == null){ // 判断fast是否为空(为空说明移动之前的fast所指节点就是要删除的节点)
break; // 跳出循环,准备删除
}
else{ // fast移动后没有指向空,说明移动之前的fast所指节点不是倒数第n个节点,此时让fast回归到原来的位置
fast = slow.next;
// 整体移动slow和fast
slow = fast;
fast = fast.next;
}
}
// 退出循环则表示slow的下一个就是要删除的节点
slow.next =slow.next.next;
return newHead.next;
}
再说一下思路:“我想象的是,每次都移动fast指针n个位置(这里会用到for循环),如果移动了n个位置之后,fast等于null了,说明原本fast的位置,就是我们要找到倒数第n个节点,然后因为slow指向的就是fast移动之前的位置,所以修改slow的next就完成了删除”
面试题 02.07. 链表相交
思路是代码随想录里的,一开始没有理解两两相交是啥意思,以为数值相等并且后面数字全相等的叫做相交,但是这样示例1相交的点也可以是1啊,然后看了一样随想录里的解释,发现是指针相等的叫做相交,思路就是先求出两个链表的长度,然后以长度更短的为循环次数,整体移动两个链表,并且比较指向两个链表的指针,是否相等,相等了就是相交了,直接返回相等的那个节点,如果循环结束还没返回,说明没有相交的点,返回null即可。
代码如下:
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if(headA == null || headB == null){
return null;
}
ListNode p = null;
ListNode pA = headA;
ListNode pB = headB;
ListNode newHead = new ListNode(-1);
int lenghtA = 0;
int lenghtB = 0;
// 下面开始计算两个链表的长度
newHead.next = headA;
p = newHead;
while(p.next != null){
p = p.next;
lenghtA++;
}
newHead.next = headB;
p = newHead;
while(p.next != null){
p = p.next;
lenghtB++;
}
int count = lenghtA<lenghtB?lenghtA:lenghtB;
// 尝试移动pA或者pB(只移动长的)
for(int i = 0; i < lenghtA - count; i++){// 进入循环了,说明lengthA是更长的
pA = pA.next;
}
for(int i = 0; i < lenghtB - count; i++){// 进入循环了,说明lengthB是更长的
pB = pB.next;
}
// 至此,pA与pB后续链表是一样长度的
// 开始相互比较,并且整体移动pA和pB
for(int i = 0; i < count; i++) { // count作为循环次数,以长度短的为循环次数
if(pA == pB){
return pA;
}else{
pA = pA.next;
pB = pB.next;
}
}
// 退出循环还没找到,说明两条链表没有相交,返回null
return null;
}
遇到的问题:
没理解相交的含义,还有求长度的时候其实可以不用new一个新的头节点,可以直接:
while (curA != null) { // 求链表A的长度
lenA++;
curA = curA.next;
}
142.环形链表II
自己思考的时候,想到了要用快慢指针,我想的是,快指针先走,如果快指针的next节点是慢指针,则慢指针的位置就是环的入口,可是问题是:快慢指针什么时候移动呢?然后就没思路了。。。看了题解视频,有点似懂非懂,最后在力扣一个题解下面看到一条评论,瞬间就开朗了
力扣题解链接:. - 力扣(LeetCode)
评论区大佬解释:. - 力扣(LeetCode)
结合大佬解释以及我下面的代码,我的理解:
代码如下:
public ListNode detectCycle(ListNode head) {
ListNode fast = head;
ListNode slow = head; // 快慢指针同起点开始移动
while(fast != null && fast.next != null) { // 因为快指针后面要走两步,为了没有空指针异常,所以需要fast和fast.next都不为空才行
fast = fast.next.next; // 快指针移动两步
slow = slow.next; // 慢指针移动一步
// 下面判断快慢指针是否相遇
if(fast == slow){
ListNode index0 = slow; // 记录相遇的这个位置
ListNode index1 = head; // 记录头节点
while(index0 != index1) { // 下面开始让这两个位置同步移动,等到这两个指针相遇的时候,便是环的入口
index0 = index0.next;
index1 =index1.next;
}
return index0;
}
}
return null;
}
链表章节的总结:
终于写完链表啦!但是旅途的中点而不是终点 |ू・ω・` )后续的题目慢慢加油吧,这篇打卡还是补卡的〒▽〒,还落了两篇打卡的,加油加油。
关于链表(个人认为重要的):
1. 链表移动结合for循环中 i 的位置来理解是关键,这篇的“删除链表的倒数第N个节点”和上一篇“设计链表”中都有关于数字下标和指针p的图解,可以帮助理解一下
2. 链表题大多数都设计“循环”、和“无效值的判断”
对于循环:
(1). 对第一个节点为空值时能否有效停止(不报空指针异常)每一道题都有循环,独立分析一下循环条件就知道了。
(2). 对于循环终止条件,什么时候停下,并且也要保证不报空指针异常。
对于无效值判断:
往往都是开头就设置if,把异常值排除,如设计链表,还有就是结合循环条件里面,头指针为空,往往也被循环条件包含了,所以即使没有进入循环,返回的空值也是符合提议的,如24. 两两交换链表中的节点中的while循环(当节点数是0,也符合是偶数,没有进入循环,但是返回的值也是正确的)