LeetCode题解142&392&876
注: 总结三道用到双指针的题目
142-环形链表Ⅱ
方法一:哈希表
思路
这其实是比较直接的思路,利用ES6的Map结构,可以对节点进行hash映射。保存所有遍历过的节点到Map结构中,每轮遍历时查看是否以遍历过此节点,如果已经遍历过,跳出循环返回结果。否则遍历完全部节点跳出并返回null
复杂度
时间复杂度:O(n),空间复杂度:O(n)
代码
/**
* Definition for singly-linked list.
* class ListNode {
* val: number
* next: ListNode | null
* constructor(val?: number, next?: ListNode | null) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
* }
*/
function detectCycle(head: ListNode | null): ListNode | null {
if(!head || !head.next) return null;
let visited = new Map, pointer = head;
// 此处映射表非常适合使用Map数据结构
while(pointer.next) {
if (visited.has(pointer)) {
break;
} else {
visited.set(pointer, pointer);
}
pointer = pointer.next;
}
return pointer.next ? pointer : null;
};
方法二:快慢指针
思路
注: 引自leetcode官方解释
我们使用两个指针,fast 与 slow。它们起始都位于链表的头部。随后,slow 指针每次向后移动一个位置,而 fast 指针向后移动两个位置。如果链表中存在环,则 fast 指针最终将再次与 slow 指针在环中相遇。
如下图所示,设链表中环外部分的长度为 a。slow 指针进入环后,又走了 bb 的距离与 fast 相遇。此时,fast 指针已经走完了环的 nn 圈,因此它走过的总距离为 a+n(b+c)+b=a+(n+1)b+nca+n(b+c)+b=a+(n+1)b+nc。
根据题意,任意时刻,fast 指针走过的距离都为 slow 指针的 22 倍。因此,我们有
a+(n+1)b+nc=2(a+b) \implies a=c+(n-1)(b+c)
a+(n+1)b+nc=2(a+b)⟹a=c+(n−1)(b+c)
有了 a=c+(n-1)(b+c)a=c+(n−1)(b+c) 的等量关系,我们会发现:从相遇点到入环点的距离加上 n-1n−1 圈的环长,恰好等于从链表头部到入环点的距离。
因此,当发现 slow 与 fast 相遇时,我们再额外使用一个指针 ptr。起始,它指向链表头部;随后,它和 slow 每次向后移动一个位置。最终,它们会在入环点相遇。
复杂度
时间复杂度:O(n),空间复杂度:O(1)
代码
/**
* Definition for singly-linked list.
* class ListNode {
* val: number
* next: ListNode | null
* constructor(val?: number, next?: ListNode | null) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
* }
*/
function detectCycle(head: ListNode | null): ListNode | null {
if(!head || !head.next) return null;
let slowPointer = head, fastPointer = head;
while (fastPointer.next && fastPointer.next.next) {
slowPointer = slowPointer.next;
fastPointer = fastPointer.next.next;
if (fastPointer === slowPointer) {
let ptr = head;
while (ptr !== slowPointer) {
ptr = ptr.next;
slowPointer = slowPointer.next;
}
return ptr;
}
}
return null;
};
392-判断子序列
方法一:双指针
思路
本质上是双指针,对t进行一次遍历,对每一个值与当前s指针处值做对比,匹配上则将s指针后移一位。
最终判断是s否有剩余未匹配数值
代码
function isSubsequence(s: string, t: string): boolean {
let sArray = s.split('');
let currentLetter = sArray.shift();
for(let i = 0; i < t.length; i++) {
if(currentLetter === t[i]) {
currentLetter = sArray.shift();
}
}
return !currentLetter;
};
876-链表的中间结点
最优方法:快慢指针
思路
利用快慢指针,两个一起后移,快指针每次后移两位,慢指针每次后移一位。快指针遍历完跳出循环,显然此时满指针所在位置就是链表中间结点
代码
/**
* Definition for singly-linked list.
* class ListNode {
* val: number
* next: ListNode | null
* constructor(val?: number, next?: ListNode | null) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
* }
*/
function middleNode(head: ListNode | null): ListNode | null {
let fp = head, lp = head;
while(fp?.next) {
fp = fp.next?.next;
lp = lp.next;
}
return lp;
};