24. 两两交换链表中的节点
思路:
两两交换节点 [1,2,3,4]。先更改指向 1 -> 3,再2 -> 1,最后 1->3
重点:
更改指向时,如何不切断整个链表的链路??
- 需临时记录两两交换节点中第二个节点的前一个节点和后一个节点,例子中就是记录 1、3。
代码:
var swapPairs = function (head) {
let dummyHead = new ListNode(0, head)
let cur = dummyHead
while (cur.next != null && cur.next.next != null) {
const temp1 = cur.next
temp2 = cur.next.next.next
cur.next = cur.next.next
temp1.next = temp2
cur.next.next = temp1
cur = cur.next.next
}
return dummyHead.next
};
19. 删除链表的倒数第N个节点
思路: 双指针又派上用场了!
定义快慢指针(slow),快指针(fast)先走 n 步,后快慢指针再同步走。当fast指向 null 的之后,slow的指向就为我们要删除的节点。
重点:
实现时快指针走 n+1 。why?!
- 因为要删除倒数第 n 个节点,就需要操作他的前一个节点,那怎么获取前一个节点呢!就需要让fast走 n+1 步,当 fast 等于 null时slow指向就是要删除节点的前一个节点了。
- fast走 n+1 带入到理论中就可以理解为fast为 null 的时候,slow往前移了一个位置
代码:
var removeNthFromEnd = function (head, n) {
// 双指针、快指针先走 n,慢指针指向要删除的节点
let dummyHead = new ListNode(0, head)
let slow = dummyHead, fast = dummyHead
while (n-- >= 0 && fast != null) {
fast = fast.next
}
while (fast != null) {
fast = fast.next
slow = slow.next
}
slow.next = slow.next.next
return dummyHead.next
};
面试题 02.07. 链表相交
思路:
遍历两个链表分别记录其长度,计算长度差 gap。长链表先走 gap 值,让长短链表尾部对齐。长短链表同时遍历,长链表起始节点则为第 gap 个节点,短链表起始节点为头节点,当找到相同节点返回当前节点,否则返回null
代码:
var getIntersectionNode = function (headA, headB) {
let lenA = 0,
lenB = 0,
curA = headA,
curB = headB;
while (curA != null) {
lenA++;
curA = curA.next;
}
while (curB != null) {
lenB++;
curB = curB.next;
}
curA = headA;
curB = headB;
if (lenA < lenB) {
[lenA, lenB] = [lenB, lenA];
[curA, curB] = [curB, curA];
}
let diff = lenA - lenB;
while (diff--) {
curA = curA.next;
}
while (curA != null) {
if (curA === curB) {
return curA;
}
curA = curA.next;
curB = curB.next;
}
return null;
};
142. 环形链表II
思路:
双指针又来喽! 快慢指针(fast、slow),让快指针走两步、慢指针走一步。如果链表有环则快指针先入环。慢指针入环后一块内快指针必追上慢指针,快慢指针重合后如何找到环入口?请看以下重点
重点:
怎样判断链表有环?
- 快慢指针,fast指针一定先进入环中,如果fast指针和slow指针相遇的话,一定是在环中相遇,这是毋庸置疑的
为什么fast指针和slow指针一定会相遇?
- 相对于slow来说,fast是一个节点一个节点的靠近slow的
- 动画如下:
快慢指针相遇后环入口怎么找?
- 因为fast指针是一步走两个节点,slow指针一步走一个节点, 所以 fast指针走过的节点数 = slow指针走过的节点数 * 2:
(x + y) * 2 = x + y + n (y + z) - 整理公式:
x = (n - 1) (y + z) + z。当 n=1 时,x = z
代码:
var detectCycle = function (head) {
let fast = head, slow = head
while (fast != null && fast.next != null) {
fast = fast.next.next
slow = slow.next
if (fast === slow) {
let index1 = head, index2 = fast
while (index1 !== index2) {
index1 = index1.next;
index2 = index2.next
}
return index1
}
}
return null
};
总结
嘿嘿。为什么直接到day5了,不告诉你🫣
链表章节至此告一段落,让我们来总结下链表算法的重点吧~
1、什么情况下使用虚拟头节点?
虚拟头节点的好处就是我们可以按照同一种逻辑来处理每个节点。
修改节点指向可以应用虚拟头节点。如只做查找操作可以不使用虚拟头节点
2、含有修改指向操作的cur指向哪个节点?
cur应该指向我们需要操作节点的前一个节点,因为当前题目前提都是单向链表,如果将cur指向需要操作的节点,如删除操作,那我们是拿不到前一个节点的。