141.环形链表
问题描述
解决方案
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @return {boolean}
*/
var hasCycle = function(head) {
let slow = head, fast = head
while(fast != null && fast.next != null) {
slow = slow.next
fast = fast.next.next
if(slow == fast) {
return true
}
}
return false
};
对于快慢指针,如果存在环,快慢指针一定会相遇
142. 环形链表II
题目描述
解决方案
- 基于环形链表I的解法,直观地来说就是当快慢指针相遇时,让其中任一个指针指向头节点,然后让它俩以相同速度前进,再次相遇时所在的节点位置就是环开始的位置。
- **原理:**我们假设快慢指针相遇时,慢指针 slow 走了 k 步,那么快指针 fast 一定走了 2k 步:
- fast 一定比 slow 多走了 k 步,这多走的 k 步其实就是 fast 指针在环里转圈圈,所以 k 的值就是环长度的「整数倍」。
- 假设相遇点距环的起点的距离为 m,那么结合上图的 slow 指针,环的起点距头结点 head 的距离为 k - m,也就是说如果从 head 前进 k - m 步就能到达环起点。
- 巧的是,如果从相遇点继续前进 k - m 步,也恰好到达环起点:
- 所以,只要我们把快慢指针中的任一个重新指向 head,然后两个指针同速前进,k - m 步后一定会相遇,相遇之处就是环的起点了。
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var detectCycle = function(head) {
let slow = head, fast = head
while(fast != null && fast.next != null) {
slow = slow.next
fast = fast.next.next
if(slow == fast) break
}
// 考虑 fast遇到空指针没有环的情况
if(fast == null || fast.next == null) {
return null
}
// 将慢指针重新指向头结点
slow = head
// 快慢指针同步前进,相交点就是环起点
while(slow != fast) {
slow = slow.next
fast = fast.next
}
return slow
};
160.相交链表
题目描述
解决方案
- 此题难点在于,两条链表的长度可能不同,两条链表之间的节点无法对应
- 如果用两个指针 p1 和 p2 分别在两条链表上前进,并不能同时走到公共节点,也就无法得到相交节点 c1。解决这个问题的关键是,通过某些方式,让 p1 和 p2 能够同时到达相交节点 c1。
① 如果用两个指针 p1 和 p2 分别在两条链表上前进,我们可以让 p1 遍历完链表 A 之后开始遍历链表 B,让 p2 遍历完链表 B 之后开始遍历链表 A,这样相当于「逻辑上」两条链表接在了一起。如果这样进行拼接,就可以让 p1 和 p2 同时进入公共部分,也就是同时到达相交节点 c1:
② 先计算两条链表的长度,然后让 p1 和 p2 距离链表尾部的距离相同,然后齐头并进,
①解法
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} headA
* @param {ListNode} headB
* @return {ListNode}
*/
var getIntersectionNode = function(headA, headB) {
let p1 = headA, p2 = headB
while(p1 != p2) {
// p1走一步,如果走到A链表末尾,转到B链表
if(p1 == null) {
p1 = headB
}else {
p1 = p1.next
}
if(p2 == null) {
p2 = headA
}else {
p2 = p2.next
}
}
return p1
};
19. 删除链表的倒数第N个节点
题目描述
解决方案
- 要删除倒数第 n 个节点,就得获得倒数第 n + 1 个节点的引用。
- 获取单链表的倒数第 k 个节点,就是想考察 双指针技巧 中快慢指针的运用,一般都会要求你只遍历一次链表,就算出倒数第 k 个节点。
- 第一步,我们先让一个指针 p1 指向链表的头节点 head,然后走 k 步:
- 第二步,用一个指针 p2 指向链表头节点 head:
- 第三步,让 p1 和 p2 同时向前走,p1 走到链表末尾的空指针时走了 n - k 步,p2 也走了 n - k 步,也就是链表的倒数第 k 个节点:
- 这样,只遍历了一次链表,就获得了倒数第 k 个节点 p2
- 解法中在链表头部接一个虚拟节点 dummy 是为了避免删除倒数第一个元素时出现空指针异常,在头部加入 dummy 节点并不影响尾部倒数第 k 个元素是什么。
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @param {number} n
* @return {ListNode}
*/
var removeNthFromEnd = function(head, n) {
// 创建一个虚拟头节点
let dummy = new ListNode(-1)
dummy.next = head
// 删除倒数第n个节点,要先找到倒数第n+1个节点
let x = findFromEnd(dummy, n+1)
// 链表操作,删除n+1个节点
x.next = x.next.next
return dummy.next
};
// 找到链表的倒数第k个节点
var findFromEnd = function(head, k) {
let p1 = head
// 只有一个元素的时候,返回 []
if(p1.next == null) {
return []
}
// p1先走k步
for(let i = 0; i < k; i++) {
p1 = p1.next
}
let p2 = head
// p1和p2 同时走n-k步
while(p1 != null){
p1 = p1.next
p2 = p2.next
}
// p2现在指向第n-k个节点 也就是倒数第k个节点
return p2
}
21. 合并两个有序列表
题目描述
解决方案
- 经典算法题:使用双指针技巧
- 这个算法的逻辑类似于「拉拉链」,l1, l2 类似于拉链两侧的锯齿,指针 p 就好像拉链的拉索,将两个有序链表合并。
- 代码中还用到一个链表的算法题中是很常见的「虚拟头结点」技巧,
也就是 dummy 节点,它相当于是个占位符,可以避免处理空指针的情况
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} list1
* @param {ListNode} list2
* @return {ListNode}
*/
var mergeTwoLists = function(list1, list2) {
// 双指针技巧
let dummy = new ListNode(-1), p = dummy
let p1 = list1, p2 = list2
while(p1 != null && p2 != null) {
// 比较p1和p2两个指针所指向的内容的大小
if(p1.val > p2.val) {
p.next = p2
p2 = p2.next
}else {
p.next = p1
p1 = p1.next
}
// p指针需保证不断前进操作
p = p.next
}
if(p1 != null) {
p.next = p1
}
if(p2 != null) {
p.next = p2
}
return dummy.next
};
23.合并K个升序链表
问题描述![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/98efecae252efd44f6f252781a415128.png)
解决方案(Java)
**利用优先级队列(二叉堆)**进行节点排序即可
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
if (lists.length == 0) return null;
// 虚拟头结点
ListNode dummy = new ListNode(-1);
ListNode p = dummy;
// 优先级队列,最小堆
PriorityQueue<ListNode> pq = new PriorityQueue<>(
lists.length, (a, b)->(a.val - b.val));
// 将 k 个链表的头结点加入最小堆
for (ListNode head : lists) {
if (head != null)
pq.add(head);
}
while (!pq.isEmpty()) {
// 获取最小节点,接到结果链表中
ListNode node = pq.poll();
p.next = node;
if (node.next != null) {
pq.add(node.next);
}
// p 指针不断前进
p = p.next;
}
return dummy.next;
}
}
// 详细解析参见:
// https://labuladong.github.io/article/?qno=23
86.分隔链表
题目描述
解决方案
自定义出两条全新的链表来获取小于x和大于等于x的节点,最后将两条链表链接起来即可
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @param {number} x
* @return {ListNode}
*/
var partition = function(head, x) {
// 分别用两条链表来进行获取内容,最后进行简单拼接即可
let dummy1 = new ListNode(-1), p1 = dummy1
let dummy2 = new ListNode(-1), p2 = dummy2
let p = head
// 通过p指针来比较每个节点应该放置在哪一条链子上
while(p != null) {
// >= 需要考虑相等的结果,相等的时候也放在第二条链子 p2上面
if(p.val >= x) {
p2.next = p
p2 = p2.next
}else {
p1.next = p
p1 = p1.next
}
// 要保持原链表,所以每个节点遍历比较完以后应该断开
let temp = p.next
p.next = null
p = temp
}
p1.next = dummy2.next
return dummy1.next
};
876.链表的中间节点
问题描述
解决方案
直接使用快慢指针的写法进行求解即可
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var middleNode = function(head) {
let slow = head, fast = head
while(fast != null && fast.next != null) {
slow = slow.next
fast = fast.next.next
}
return slow
};