文档讲解:代码随想录
状态:已完成
首先肯定是第一时间想到用虚拟头节点的方式的,因为这样会方便很多,要不然每次针对头结点(没有前一个指针指向头结点),还要单独处理。
对于交换结点时 我们需要特别注意交换的顺序,不然操作起来很容易乱。
操作之后,链表如下:
代码如下:
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode dumHead = new ListNode(-1,head);//设置一个虚拟头结点,并指向head
ListNode current = dumHead; //创建一个指针指向 虚拟头结点
ListNode tempOne; //临时结点,保存两个节点中第一个结点
ListNode tempTwo; //临时结点,保存两个节点中的第二个结点
while(current.next != null &¤t.next.next != null){
tempOne = current.next;
tempTwo = current.next.next.next;
current.next = current.next.next;//步骤一
current.next.next = tempOne;//步骤二
tempOne.next = tempTwo;//步骤三
current = current.next.next;//current移动,准备下一轮的交换
}
return dumHead.next;
}
}
在阅读上述代码时,一定要仔细看边上的注释,对于每行代码干啥的都写得很清楚。交换过程不清楚就看上述几张图片。
还是使用虚拟头节点的方式
如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了。
fast首先走n + 1步 ,为什么是n+1呢,因为只有这样同时移动的时候slow才能指向删除节点的上一个节点(方便做删除操作),如图:
代码如下:
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dumHead = new ListNode(-1,head);//创建虚拟头节点并指向head
ListNode fast = dumHead;//快指针
ListNode slow = dumHead;//慢指针
//实际循环次数为 n+1,因为需要快指针多走一步,那这样最后删除结点前,慢指针会指向删除结点的前驱
for(;n >= 0 && fast != null;n--){
fast = fast.next;
}
//快慢指针同时移动,循环结束之后慢指针会指向删除结点的前驱
while(fast !=null){
fast = fast.next;
slow = slow.next;
}
//删除结点
slow.next = slow.next.next;
return dumHead.next;
}
}
理解该题需要理解上述代码和图片,需要仔细阅读
其实刚开始是想着两层while循环嵌套直接查找的,但是后面看到别人说的一句“当两链表有交点时,那么他们交点及之后的长度是一定是一致的”,这一下整个思路清晰明了。
其实,简单来说,这道题目就是就是求两个链表交点节点的指针【交点不是数值相等,而是指针相等】
我们求出两个链表的长度,并求出两个链表长度的差值,然后让curA移动到,和curB 末尾对齐的位置,如图:
此时我们就可以比较curA和curB是否相同,如果不相同,同时向后移动curA和curB,如果遇到curA == curB,则找到交点。
具体代码如下,需要据上图去理解下述代码
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
//整体思路:当两链表有交点时,那么他们交点及之后的长度是一定是一致的
int lenA = 0;//链表A的长度
int lenB = 0;//链表B的长度
//定义两个指针,一个指向链表A的head,一个指向B的head
ListNode curA = headA;
ListNode curB = headB;
//下述两个while循环用来得出两链表的长度
while(curA != null){
curA = curA.next;
lenA++;
}
while(curB != null){
curB = curB.next;
lenB++;
}
//重新指向头节点
curA = headA;
curB = headB;
//算出两链表的长度差
int num = lenA - lenB;
if(num >= 0){//A的长度 大于等于 B的长度
for(;num > 0;num--){
curA = curA.next;
}
while(curA != null&&curB != null){
if(curA == curB){
return curA;
}
curA = curA.next;
curB = curB.next;
}
}else{//A的长度 小于 B的长度
for(;num < 0;num++){
curB = curB.next;
}
while(curB != null&&curB != null){
if(curA == curB){
return curB;
}
curA = curA.next;
curB = curB.next;
}
}
return null;
}
}
建议:在看该题思路之前最好把题解和卡哥的讲解视频看一遍
《代码随想录》算法视频公开课 (opens new window):把环形链表讲清楚!| LeetCode:142.环形链表II
视频连接:
其实刚看这道题目时,我是很蒙的。无从下手,所以我就先看卡哥的视频去梳理整个的解题思路
这道题目,不仅考察对链表的操作,而且还需要一些数学运算。
主要考察两知识点:
就是使用快慢指针,定义 fast 和 slow 指针,一起从头结点出发,fast 每次移动 两个结点,slow 每次移动一个结点,如果两个指针在中途相遇了,就一定有环。
所以这里就有个问题了,为什么这两个指针在有环的情况下一定会相遇呢,这里我就用卡哥给的解释:
可以画一个环,然后让 fast指针在任意一个节点开始追赶slow指针。
会发现最终都是这种情况, 如下图:
fast和slow各自再走一步, fast和slow就相遇了
这是因为fast是走两步,slow是走一步,其实相对于slow来说,fast是一个节点一个节点的靠近slow的,所以fast一定可以和slow重合。
动画如下:
到了此处就代表我们已经知道了该链表是有环的,那么如何找到这个环的入口呢?
这里我也用卡哥给的解释:
假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入口节点节点数为 z。 如图所示:
那么相遇时: slow指针走过的节点数为: x + y
, fast指针走过的节点数:x + y + n (y + z)
,n为fast指针在环内走了n圈才遇到slow指针, (y+z)为 一圈内节点的个数A。
因为fast指针是一步走两个节点,slow指针一步走一个节点, 所以 fast指针走过的节点数 = slow指针走过的节点数 * 2:
(x + y) * 2 = x + y + n (y + z)
两边消掉一个(x+y): x + y = n (y + z)
因为要找环形的入口,那么要求的是x,因为x表示 头结点到 环形入口节点的的距离。
所以要求x ,将x单独放在左面:x = n (y + z) - y
,
再从n(y+z)中提出一个 (y+z)来,整理公式之后为如下公式:x = (n - 1) (y + z) + z
注意这里n一定是大于等于1的,因为 fast指针至少要多走一圈才能相遇slow指针。
这个公式说明什么呢?
先拿n为1的情况来举例,意味着fast指针在环形里转了一圈之后,就遇到了 slow指针了。
当 n为1的时候,公式就化解为 x = z
,
这就意味着,从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点。
也就是在相遇节点处,定义一个指针index1,在头结点处定一个指针index2。
让index1和index2同时移动,每次移动一个节点, 那么他们相遇的地方就是 环形入口的节点。
动画如下:
那么 n如果大于1是什么情况呢,就是fast指针在环形转n圈之后才遇到 slow指针。
其实这种情况和n为1的时候 效果是一样的,一样可以通过这个方法找到 环形的入口节点,只不过,index1 指针在环里 多转了(n-1)圈,然后再遇到index2,相遇点依然是环形的入口节点。
代码:
/**
* 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 slow = head;
ListNode fast = 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;
}
}
以上为我做题时候的相关思路,语言组织能力较弱,有错误望指正。