24.两两交换链表中的节点
题目链接:24. 两两交换链表中的节点 - 力扣(LeetCode)
视频链接:帮你拿下反转链表 | LeetCode:206.反转链表 | 双指针法 | 递归法_哔哩哔哩_bilibili
讲解链接:代码随想录 (programmercarl.com)
整体思路
还是用dummyhead。dummyhead.next=head
上来先cur=dummyhead
。用两个tmp保存1和3号节点,分3步完成交换。
然后更新cur,最后return dummyhead.next
-
关键:操作位cur 一定要指向 待交换两个节点的 前一个节点!
-
交换的过程:
- 1.先让cur指向2
- 2.再让2指向1
- 3.最后让1指向3
-
遍历的终止条件?
每次交换前可以判断一下,cur.next
和 cur.next.next
是否存在!
如果节点数量为偶数,cur.next为null了,不进行交换,直接return头节点。(空链表的情况也符合)
如果节点数量为奇数,cur.next存在但cur.next.next为null,就不进行交换,直接return 头节点。
如果都存在,那么才能进行交换。
所以遍历条件是:while(cur.next!=null && cur.next.next!=null)
。(两种终止情况都包含在里面了,&&运算符是从左到右的!)
- 具体的交换方式:
注意:cur在一次交换的三步里都没有变过!!!
ListNode tmp1 = cur.next; //先把1号节点保存一下
cur.next = cur.next.next; //cur.next=2号节点
ListNode tmp2 = cur.next.next;//再把3号节点保存一下
cur.next.next = tmp1;//2号节点.next=1号节点
tmp1.next = tmp2; //1号节点.next=tmp
此时,两个节点已经交换好了,接着更新cur
cur=cur.next.next
注意:最后return dummyhead.next
!!! 为什么不能return head?
- 因为head一直都是指向1号节点,没有变过,不能这样返回。
完整代码
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode dummyhead = new ListNode();
dummyhead.next = head;
ListNode cur = dummyhead;
while(cur.next != null && cur.next.next != null) {
ListNode tmp1 = cur.next; //先把1号节点保存一下
cur.next = cur.next.next; //cur.next=2号节点
ListNode tmp2 = cur.next.next;//再把3号节点保存一下
cur.next.next = tmp1;//2号节点.next=1号节点
tmp1.next = tmp2; //1号节点.next=3号节点
cur = cur.next.next; //更新cur
}
return dummyhead.next;
}
}
19.删除链表的倒数第N个节点
题目链接:19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)
视频链接:链表遍历学清楚! | LeetCode:19.删除链表倒数第N个节点_哔哩哔哩_bilibili
讲解链接:代码随想录 (programmercarl.com)
双指针法
用虚拟头节点,定义两个快慢指针,要删除倒数第n个节点,让fast指针先走n步,再让fast和low一起走,直到fast指向链表末尾。再删掉slow指向的就行了。
注意细节部分:
- 快慢指针为虚拟头节点。
- fast先走n步,fast最后停在倒数第一个节点。此时slow指向倒N的前一个位置,方便删除
- 删除照样要用tmp保存一下
- 最后return dummyhead.next
完整Java代码:
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummyhead = new ListNode();
dummyhead.next = head;
ListNode fast = dummyhead;
ListNode slow = dummyhead;
for(int i = 0; i < n;i++) { //fast先往后走n步
fast = fast.next;
}
while(fast.next != null) {
slow = slow.next;
fast = fast.next;
}
//此时slow.next就是倒数第N个节点
ListNode tmp = slow.next.next;
slow.next = tmp;
return dummyhead.next;
}
}
面试题 02.07.链表相交
题目链接:面试题 02.07. 链表相交 - 力扣(LeetCode)
讲解链接:代码随想录 (programmercarl.com)
思路
简单来说,就是求两个链表交点节点的指针。
先求出两个链表的长度,然后求出长度差值,让长的作为链表A。让curA移动到与curB末尾对齐的位置。怎么移?
**然后curA和curB相当于在同一起跑线了。**此时比较curA和curB是否相同。
如果不相同,同时向后移动curA和curB,如果遇到curA == curB,则找到了交点,即curA。
否则循环退出返回null。
- A和B有一个空链表的情况:直接返回null
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if(headA == null || headB == null) return null;
int countA = 0;
int countB = 0;
ListNode curA = headA;
ListNode curB = headB;
while(curA != null) {
countA++;
curA = curA.next;
}
while(curB != null) {
countB++;
curB = curB.next;
}
if(countB > countA) { //让长的成为A链表
ListNode tmp = headA;
headA = headB;
headB = tmp;
}
curA = headA;
curB = headB;
//让curA往后移动差值步,curA和curB在同一起跑线了
for(int i = 0; i < Math.abs(countB - countA); i++) {
curA = curA.next;
}
while(curA != null) {
if(curA == curB) return curA;
curA = curA.next;
curB = curB.next;
}
return null;
}
}
142.环形链表II
题目链接:142. 环形链表 II - 力扣(LeetCode)
视频链接:把环形链表讲清楚! 如何判断环形链表?如何找到环形链表的入口? LeetCode:142.环形链表II_哔哩哔哩_bilibili
讲解链接:代码随想录 (programmercarl.com)
双指针判断环
用双指针,快指针一次走2步,慢指针一次走1步。快指针一定先进入环,在慢指针也进入环后,实际就是快追慢的过程(套圈),每次差值是1步,环长又是1的倍数,最终一定能追上(如果有环的话)。
- 慢指针进入环时,两指针相差的节点数一定是小于一圈的!因此相遇时慢指针不可能走满了一圈
找到环的入口
定义变量:
- 起始位置到入口处为x,入口处到相遇点为y,从y再到入口处为z。
根据这三个定义,结合快指针走2,慢指针走1,可以用一个等式连接!
设快指针在环里走了n圈才相遇。
快指针走过的路径长:n * (y + z) + x + y
(这里的n圈是从快指针第一次进环 走到交点处开始算的n圈)
慢指针走过的路径长:x + y
再由快 = 慢 * 2:
n * (y + z) + x + y = (x + y) * 2
n一定是大于等于1圈的(因为慢指针不是停住不动的!)。能让一圈出来,变成(n-1)
变形成:n(y+z)-(y+z)+(y+z)-y
化简得:x = (n-1)(y+z) + z
对这个等式的理解:从相遇点出发的指针从入口路过时总能且仅能和从x出发的指针相遇(相同的速度)。即使转了很多圈,交点处也不会改变
- 假设n=1的情况下,推断出
x=z
!!!
也可以这样理解:z是快指针比慢指针多跑的距离,快每次多跑1个,当把环中整圈的距离去掉后,剩下的就是快指针在环外多跑的距离,就是x
这就意味着,从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点。
也就是在相遇节点处,定义一个指针index1,在头结点处定一个指针index2。
让index1和index2同时移动,每次移动一个节点, 那么他们相遇的地方就是 环形入口的节点。
代码实现
while遍历时判断while(fast!=null && fast.next!=null)
public class Solution {
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) {
ListNode index1 = fast;
ListNode index2 = head;
while(index1 != index2) {
index1 = index1.next;
index2 = index2.next;
}
//while结束后就找到了入口!
return index1;
}
}
return null;
}
}
链表大总结
-
最后都是
return dummyhead.next
-
要想到双指针。都从
dummyhead
开始比较好 -
如果要删除或者修改方向记得用tmp保存
-
注意遍历while的终止条件!考虑停下来的最终情况(第一次不满足时),此时就是
while()
括号里的条件,走到这个情况了就不会进入while了。 -
要删除或者插入节点,要让cur指向它的前一个。
-
求两个链表交点就是让长的那个先走长度的差值步,再从同一起跑线一起走。
-
一次走两步,遍历条件是while(fast!=null && fast.next!=null)