【JS/TS算法系列】LeetCode题解142、392、876-双指针算法专题

本文介绍了LeetCode中三道使用双指针解决的链表问题,包括142题(环形链表II)、392题(判断子序列)和876题(链表的中间结点)。通过哈希表和快慢指针的方法,详细解析了如何找出链表环的入口、判断字符串是否为链表的子序列以及找到链表的中间节点。这些方法的时间复杂度为O(n),空间复杂度在O(1)到O(n)之间,展示了双指针在解决链表问题上的高效性。
摘要由CSDN通过智能技术生成

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;
};
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值