24.两两交换链表中的节点
解题方法:虚拟头节点
解题步骤(如图示)
说明:创建一个虚拟头节点(dummyHead),定义一个 cur 指针指向虚拟头节点用来遍历链表
需要思考和注意的问题:
- 遍历链表过程中,循环结束的条件是什么?
- 链表节点交换过程中是否需要保存部分节点位置?
- 交换完后
cur指针该如何移动?
1. 循环遍历链表,结束的条件是什么?
-
本题中,要想操作两个节点,指针必须指向两个节点的前一个节点(换句话说就是,指向第一个节点的指针决定后面两个节点的交换)。
-
链表节点为奇数个时,需要判断
cur.next.next是否为null -
链表节点为偶数个时,需要判断
cur.next是否为null -
在写结束条件的时候,应该把
cur.next的判断写在cur.next.next之前,确保不会出现空指针异常(细节点,需要注意!!!)代码表示为:
while(cur.next != null && cur.next.next != null)
2.交换链表节点过程中是否需要保存部分节点?
需要保存的节点位置:
- 两个交换节点中第一个节点的位置
- 两个交换节点后面一个节点的位置
3.交换完后cur指针该如何移动?
- cur 指针应该向后移动两个位置
解题代码:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode dummyHead = new ListNode(-1);
dummyHead.next = head;
ListNode cur = dummyHead;
//定义两个临时节点保存第一个节点和第三个节点的位置
ListNode temp;
ListNode temp2;
while(cur.next != null && cur.next.next != null){
temp = cur.next;
temp2 = cur.next.next.next;
cur.next = cur.next.next;
cur.next.next = temp;
temp.next = temp2;
//交换完后cur指针向后移动2位
cur = cur.next.next;
}
return dummyHead.next;
}
}
19.删除链表的倒数第N个节点
解题方法:虚拟头节点 + 双指针
初始状态:定义一个虚拟头节点,定义快慢两个指针指向虚拟头节点
指针移动过程:
需要思考的问题:
1.为什么 fast 指针要先走 n+1 步?
1.为什么 fast 指针要先走 n+1 步?
如果 fast指针 先走 n 步 ,然后 fast指针 和 slow指针 再同时移动 ,这样当 fast指针 指向 null 时,slow指针 刚好指向要删除的那个节点;
但是要删除节点需要得到它的前一个节点,所以应该让 fast指针 先走 n+1 步,再同时移动,这样 fast指针 指向 null 时,slow指针 就刚好指向要删除节点的前一个节点;
解题代码:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummyHead = new ListNode(-1);
dummyHead.next = head;
//定义快慢指针指向虚拟头节点
ListNode fastIndex = dummyHead;
ListNode slowIndex = dummyHead;
//先让快指针走n+1步
for(int i = 0;i <= n;i++){
fastIndex = fastIndex.next;
}
//快慢指针一起移动,结束时慢指针刚好指向删除节点的前一个节点
while(fastIndex != null){
fastIndex = fastIndex.next;
slowIndex = slowIndex.next;
}
//移除节点
slowIndex.next = slowIndex.next.next;
return dummyHead.next;
}
}
面试题 02.07.链表相交
思路:求两个链表交点节点的指针
注意: 交点不是数值相等,而是指针相等
如下两个链表,curA 指向链表A的头结点,curB 指向链表B的头结点
-
分别计算A、B两个链表的长度(计算完后别忘了让指针归位,很容易遗忘这个细节!!!)
-
让
curA移动到和curB对齐的位置
- 比较
curA和curB是否相同- 相同,则为交点,返回
- 不同,同时向后移动
curA和curB,继续比较curA和curB
注意:是比较指针,并不是比较指针对应的值(刚开始写的时候就比较成值了。。。)
解题代码
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
//定义两个指针分别指向两个链表的头节点
ListNode curA = headA;
ListNode curB = headB;
//分别计算两个链表的长度
int lengthA = 0;
int lengthB = 0;
while(curA != null){
lengthA++;
curA = curA.next;
}
while(curB != null){
lengthB++;
curB = curB.next;
}
//让指针归位
curA = headA;
curB = headB;
//如果B链表长度大于A,就交换两个链表的长度和指针,保证A链表的长度大于B链表
//定义临时指针和长度用于交换
if(lengthB > lengthA){
int tempLength = 0;
ListNode tempCur;
tempLength = lengthB;
lengthB = lengthA;
lengthA = tempLength;
tempCur = curB;
curB = curA;
curA = tempCur;
}
//计算两个链表的长度差
//int n = Math.abs(lengthA - lengthB);
int n = lengthA - lengthB;
//移动curA让其与curB起始位置相同
for(int i = 0;i < n;i++){
curA = curA.next;
}
//此时两个链表的长度相同,遍历curA即可
while(curA != null) {
//相同,找到交点
if(curA == curB){
return curA;
}else{
curA = curA.next;
curB = curB.next;
}
}
return null;
}
}
142.环形链表Ⅱ
解题思路:数学思维+双指针
需要思考的问题:
- 如何确定这个链表是否有环
- 如果有环,应该如何找到环的入口处
1.如何确定这个链表是否有环
定义快慢两个指针 快指针(fast)每次移动 2 个节点 ,慢指针(slow)每次移动 1 个节点,
如果 fast == slow ,则证明链表有环
说明:fast 指针 一定先进入环中,如果 fast指针 和 slow指针 相遇的话,一定是在环中相遇。
为什么fast指针和slow指针一定会相遇呢?
因为 fast 是走两步,slow 是走一步,相对于 slow 来说,fast 是一个节点一个节点的靠近 slow 的,所以fast一定可以和slow重合
2.如果有环,应该如何找到环的入口处
思路:
-
假设从头结点到环形入口节点 的节点数为 x 。
-
环形入口节点到 fast指针 与 slow指针 相遇节点的节点数为 y。
-
从相遇节点 再到环形入口节点节点数为 z。
如图所示(来源:代码随想录):
快慢指针相遇时:
slow 指针 走过的节点数 : x + y
fast 指针 走过的节点数 : x + y + n*( z + y )
n 为 fast 指针 在环内走过的圈数,z + y 为一圈内所走的节点数
因为 fast 指针 速度是 slow 指针 的两倍 :
所以 fast 指针 走过的节点数 = slow 指针 走过的节点数 * 2,
即 2 * ( x + y ) = x + y + n * ( z + y )
整理得:x = n * (y + z) - y ,提出一圈 y + z 后 ====> x = ( n - 1 )( y + z ) + z
假设 n = 1 的情况,意味着 fast指针 在环形里转了一圈之后,就遇到了 slow指针 此时公式为 x = z,
这就意味着,从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点
如果 n > 1 的情况发生,就是 fast指针 在环形转 n 圈之后才遇到 slow指针
解题代码:
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* 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;
}
//找到入口,返回
return index1;
}
}
return null;
}
}
补充说明:
为什么第一次在环中相遇,slow的 步数 是 x + y 而不是 x + y + 若干环的长度 呢?
这个就借用一下代码随想录网站里的解释吧。。。。。。
首先 slow 进环的时候,fast 一定是先入了进环。
如果slow进环入口,fast也在环入口,那么把这个环展开成直线,就是如下图的样子:
如果 slow 和 fast 同时在环入口开始走,一定会在环入口3相遇,slow走了一圈,fast走了两圈。
重点来了,slow 进环的时候,fast 一定是在环的任意一个位置,如图:
那么 fast 指针走到环入口3的时候,已经走了k + n个节点,slow指针相应的应该走了(k + n)/2个节点。
因为 k 是小于 n 的,所以 (k + n) / 2 一定小于 n。
也就是说 slow 一定没有走到环入口3,而 fast 已经到环入口3了。
说明在 slow 开始走的那一环就已经和 fast 相遇了。