这是跟着代码随想录的顺序学习算法的第六天。
以下是学习题解时自己的一些理解与笔记,有错误欢迎指正与讨论。
19. 删除链表的倒数第 N 个结点
参考相关链接:
19. 删除链表的倒数第 N 个结点
142. 环形链表 II
代码随想录
笔记
19.删除链表的倒数第 N 个结点
题目给出提示,用一次扫描来完成,也就是遍历一次,所以想到了双指针法,此时两个指针同向移动,一个指针负责历遍,一个指针负责满足题目条件。
注意到,n
是提前给定的,末尾是固定的(遍历的终点),所以需要删除的点的位置也是可以在一次遍历中确定的。
可以想象负责遍历的指针到达终点的时候,距离需要删除的点中间有一根杆子,长度就是 n
,固定不变,所以可以利用这一点,反向推导,只要一开始就有这根杆子且另外一个慢指针在另一端,那么结束遍历的时候,慢指针就会停在期望的位置,此时再进行删除操作即可。
var removeNthFromEnd = function(head, n) {
const virNode = new ListNode(0, head);
let fast = virNode;
let slow = virNode;
let distance = 0;
while (fast.next != null) {
if(distance === n){
fast = fast.next;
slow = slow.next;
} else {
fast = fast.next;
distance++;
}
}
slow.next = slow.next.next;
return virNode.next;
};
142. 环形链表 II
本题主要难点在于之前没有遇到过循环链表这样无法一个指针完成历遍的链表,应对方案则是采用两个错位的指针,一个快指针一次走两个节点,一个慢指针一次走一个节点,这样一来,如有环存在则快指针一定能够追上慢指针(速度相差一个节点,相当于在一条直线上,快指针逐步缩短与慢指针之间的距离)。
另外一个难点在于如何寻找环的入口,假设 head
距离环的入口相距 x
,环的入口距离快慢指针相遇的地方相距 y
,快慢指针相遇的地方距离环的入口相距 z
,注意,此处指的距离是按照头节点开始一直往后推进的方向来取的,故环的长度为 y+z
。
不妨假设快指针在与慢指针相遇之前已经在循环中转了 n
圈,其中,n>=1
,因为快指针第一次进入环的时候是不可能与慢指针相遇的。
则二者相遇时走过的路程关系可记为, 2*(x+y) = x + n*(y+z) + y
,因为快指针速度始终是慢指针的两倍。
最终需要求的结果是 x
,故化简得 x = (n-1)*(y+z) + z
,当 n = 1
时,有 x = z
,这意味着,此时从头节点到环的入口的距离 x
与从快慢指针相遇的地方到环的入口的距离 z
是相等的。注意到,化简后的式子中除了这两个参数,还有一项是 (n-1)*(y+z)
,即 n-1
个环的长度,而绕着环转整数圈又是一定会回到起始位置的。
所以,可以从另一个角度看待这个化简后的等式,一个指针 A
从头节点出发(速度为 1
节点),另一个指针 B
从快慢指针相遇的地方出发(速度为 1
节点),根据等式可知,二者一定会在环的入口处相遇( B
到了环入口之后,又多转了 n-1
圈,但最终还是回到入口处与 A
相遇)。
var detectCycle = function(head) {
if (head === null) { // 判断临界条件,后续的while条件无法判断的情况
return head;
}
let fast = head;
let slow = head;
while (fast.next != null && fast.next.next != null) { // 若一节点指向null,则无环
fast = fast.next.next;
slow = slow.next;
if (slow === fast) { // 开始寻找环的入口
let cur1 = head;
while (cur1 !== slow) {
cur1 = cur1.next;
slow = slow.next;
}
return cur1; // cur1 === slow
}
}
// 不确定是 下一个节点 还是 下下个节点 为空
return fast.next === null ? fast.next : fast.next.next;
};