开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情
- 24.两两交换链表中的节点
- 19.删除链表的倒数第N个节点
- 面试题 02.07. 链表相交
- 142.环形链表II
该博客内容参考Carl老师的《代码随想录》,记录一些自己的所思所想,以及卡神的解题思路。
24.两两交换链表中的节点
题目链接:24. 两两交换链表中的节点 - 力扣(LeetCode)
错误想法
建议使用虚拟头结点,这样会方便很多,要不然每次针对头结点(没有前一个指针指向头结点),还要单独处理。
我的思路以及瓶颈
题目给的测试用例是:head = [1,2,3,4]
- 那么现在有一个问题就是说,如何将cur定位到2这里?
- 注意到18行,说明17行中虚拟头结点的设置成功。现在的链表为:0(虚拟)➡️1➡️2➡️3➡️4
- 因为要两两交换,所以先设置一个 len 方便后续使用。设置临时指针
cur=dummyNode,那么可以看到cur.next就是原来的链表了! - 接下来就是不断移动cur指针,当cur指针指向 2 的时候,来个“乾坤大挪移”!
可以看到26行中,cur打印的结果就是[2,3,4]。说明cur现在指向的就是 2 。
- temp 用来暂时存一下2.next之前的指针,这样指针改变后,原来指向3的地址就不会丢失。
dummyNode.next.next = temp;就是将 1 的指针指向 3.- 瓶颈: 目前只能交换头部两个节点。然后链表的长度貌似也打印不出来。dummyNode.length 输出的还是 undefined。
卡神解法
var swapPairs = function (head) {
let ret = new ListNode(0, head), temp = ret;
while (temp.next && temp.next.next) {
let cur = temp.next.next, pre = temp.next;
pre.next = cur.next;
cur.next = pre;
temp.next = cur;
temp = pre;
}
return ret.next;
};
-
运行
let ret = new ListNode(0, head), temp = ret;过后,目前temp就是[0, 1, 2, 3, 4] -
let cur = temp.next.next, pre = temp.next;解读:这个cur指向了2,pre指向了1
-
pre.next = cur.next;解读:让 1 指向 2.next ,即:1 ➡️ 3
-
cur.next = pre;解读:cur还是2, 2 ➡️ 1 。
-
temp.next = cur;解读:那么得让虚拟头结点指向2 。则temp就是[0, 2, 1, 3, 4]
-
temp = pre;解读:如果不写这一句,那么执行结果会超出时间限制。原因:如果没有这一句,那么本次循环终止,进入下次循环,那么 temp.next 和 temp.next.next 又变成了 2 和 1 。那么就会无限循环下去。⭐️而使用了 temp = pre ,pre之前就是 数值为 1 的这个节点,相当于下次循环时,1.next 和 1.next.next 就变成了 3和4 。实在是妙哇!!
19.删除链表的倒数第N个节点
题目链接:19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)
双指针的操作,要注意,删除第N个节点,那么我们当前遍历的指针一定要指向 第N个节点的前一个节点
卡神解法
- 这道题目的关键是要找到
倒数第n个节点 - 快指针先移动n步,然后快慢指针再同时移动,直到快指针指向了null。那么时候slow指针指向的就是我们要删除的那个节点。
- 但是实际上,我们一定要让指针指向要删除的节点的前一个节点。这样,前一个节点➡️要删除节点的next,就能完成删除节点的操作。
- 所以,快指针要多走一步,然后快慢指针再同时移动
var removeNthFromEnd = function(head, n) {
let ret = new ListNode(0, head), // 创建虚拟头结点
slow = fast = ret;
while(n--) fast = fast.next;
while (fast.next !== null) {
fast = fast.next;
slow = slow.next
};
slow.next = slow.next.next;
return ret.next;
};
结合测试用例:[1,2,3,4,5] , n = 2 来解释一下代码步骤
创建虚拟头结点后,此时的 slow = fast = ret 都为 [0, 1, 2, 3, 4, 5]
-
while(n--) fast = fast.next;解读:此时快指针走了 n 步数,
-
fast = fast.next;解读:实际上要求 fast 走 n+1 步数,所以在
while (fast.next !== null)中,fast 又走了一步 -
自己画画图也可以得知,进过 while 循环过后,此时的 slow 指向了要删除的节点的前一个节点。在用例中就是指向了 3 。
-
slow.next = slow.next.next;只要让 3 ➡️ 5 ,那么就能删除4 。
面试题 02.07. 链表相交
题目链接:面试题 02.07. 链表相交 - 力扣(LeetCode)
卡神解法
注意点:交点不是数值相等,而是指针相等。
首先需要知道链表的长度,由于链表和数组不同,不能通过了,length属性求出长度。所以需要自己写一个求长度的函数。
var getListLen = function(head) {
let len = 0, cur = head;
while(cur) {
len++;
cur = cur.next;
}
return len;
}
- 其中还是需要一个临时指针 cur ,
cur = head,用于后面遍历链表。
其次,求出两个链表的长度,并求出两个链表长度的差值。然后让curA移动到,和curB 末尾对齐的位置
var getIntersectionNode = function(headA, headB) {
let curA = headA,curB = headB,
lenA = getListLen(headA),
lenB = getListLen(headB);
if(lenA < lenB) {
// 下面交换变量注意加 “分号” ,两个数组交换变量在同一个作用域下时
// 如果不加分号,下面两条代码等同于一条代码: [curA, curB] = [lenB, lenA]
[curA, curB] = [curB, curA];
[lenA, lenB] = [lenB, lenA];
}
let i = lenA - lenB;
while(i-- > 0) {
curA = curA.next;
}
while(curA && curA !== curB) {
curA = curA.next;
curB = curB.next;
}
return curA;
};
-
长度的差值需要大于 0 ,所以通过 if 后面的语句段,让 lenA 设为长度更长的链表。
-
while(i-- > 0) { curA = curA.next; }解读:让 curA 移动到,和 curB 末尾对齐的位置(借用一下大佬的图) !
-
while(curA && curA !== curB)解读:如果 curA 现在所指不为空,而且和 curB 不相等。那么两个指针继续移动一格。
142.环形链表II
题目链接:142. 环形链表 II - 力扣(LeetCode)
// 两种循环实现方式
/**
* @param {ListNode} head
* @return {ListNode}
*/
// 先判断是否是环形链表
var detectCycle = function(head) {
if(!head || !head.next) return null;
let slow =head.next, fast = head.next.next;
while(fast && fast.next && fast!== slow) {
slow = slow.next;
fast = fast.next.next;
}
if(!fast || !fast.next ) return null;
slow = head;
while (fast !== slow) {
slow = slow.next;
fast = fast.next;
}
return slow;
};
var detectCycle = function(head) {
if(!head || !head.next) return null;
let slow =head.next, fast = head.next.next;
while(fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
if(fast == slow) {
slow = head;
while (fast !== slow) {
slow = slow.next;
fast = fast.next;
}
return slow;
}
}
return null;
};