[typescript] 链表类算法题型与方法总结

链表类题型刷了大概20道左右的样子,总结出一些规律,分享给大家

解题思路

最主要的思路是递归和迭代两种,其中又包含一些技巧性的操作,例如快慢指针这种,总结出来的这些解题思路,应对大多数的链表类型题应该问题不大了

案例1: 倒数第k个节点

参考题:

  • 剑指offer22 链表中倒数第k个节点 简单
  • LC19 删除链表中的倒数第N个节点 中等

主要思路是快慢指针,让快指针先走k步,再一起前进,快指针走完的时候,慢指针此时所在位置就是倒数第k个节点

const getKthFromEnd: ListNode | null = (head: ListNode | null, k: number) => {
    let slow: ListNode = head, fast: ListNode = head
    for (let i = 0; i < k; i++){
        fast = fast.next
    }
    while (fast){
        fast = fast.next
        slow = slow.next
    }
    return slow
};

这个思路可以紧接着套用到删除倒数第n个节点,只要找到倒数第n+1个节点的引用即可

const removeNthFromEnd: ListNode | null = (head: ListNode | null, n: number) => {
    let dummy: ListNode = new ListNode(-1)
    dummy.next = head
    const p: ListNode = getKthFromEnd(dummy, n+1)
    p.next = p.next.next
    return dummy.next
}

案例2: 环形链表/寻找链表中点

参考题:

  • LC141 环形链表
  • LC142 环形链表II
  • LC876 链表的中间节点

快慢指针的第二种常见应用场景,这个类型的题中,快指针走两步,慢指针走一步。由于快慢指针的方法趋同,所以把这两个类型的题型放到一起讲
对于环形链表1,可以这么理解,每次让快指针走两步,慢指针走一步,一旦进入环,慢指针和快指针终究会相遇,从头到尾没相遇就是没有环
代码如下

const hasCycle: boolean = (head: ListNode | null) => {
    let slow: ListNode = head
    let fast: ListNode = head
    while (fast && fast.next) {
        slow = slow.next
        fast = fast.next.next
        if (slow === fast){
            return true
        }
    }
    return false
};

对于环形链表II,题目要求返回环的起点索引,这个可以先判断是否有环,如果有环则等待下一步操作,无环返回null
若有环,在快慢指针重合的那个节点开始,让慢指针回到head头节点,快慢指针以同样的速度前进,若快慢指针再次相遇时这个点就是环的起点。
这句话怎么理解呢?首先要承认,快指针走2k步时,慢指针正好走了k步,恰巧此时快慢指针相遇,那么快指针比慢指针多走的k步实际上就是环长度的整数倍。假设第一次相遇的节点距离环的起始位置为m个节点,且一圈环的长度正好是k步,那么慢指针距离环起点的距离为k-m,快指针从环里出发到达起点的距离也为k-m。这里的思路参考labuladong的算法小抄。

const detectCycle: ListNode | null = (head: ListNode | null) => {
    let slow: ListNode = head
    let fast: ListNode = head
    while (fast && fast.next) {
        slow = slow.next
        fast = fast.next.next
        if (fast === slow){
            break;
        }
    }
    if (fast === null || fast.next === null) {
        return null
    }
    slow = head
    while (slow !== fast) {
        slow = slow.next
        fast = fast.next
    }
    return slow
};

寻找链表中点也非常相似,也是慢指针1步,快指针两步,快指针走到的时候,慢指针正好走到中间,这里需要考虑链表长度的奇偶问题,可能会产生一些影响

案例3: 反转链表

参考题:

  • LC206 反转链表 简单
  • LC234 回文链表 简单
  • LC92 反转链表II 中等
  • LC25 K个一组反转链表 困难

以最简单的反转链表为例子,给定一个头节点,返回一个反转的链表,typescript的模版如下, 使用迭代的方法,先保存cur.next到一个新变量,再每次修改cur的指针指向pre,向前移动pre和cur。

const reverse: ListNode | null = (head: ListNode | null) => {
    let pre: ListNode = null, cur: ListNode = head
    while (cur){
        let temp = cur.next
        cur.next = pre
        pre = cur
        cur = temp
    }
    return pre
}

回文链表基于上面的例子,就很简单了,就是一个寻找链表中点+反转链表的例子,代码如下:

const isPalindrome: boolean = (head: ListNode | null) => {
    let slow: ListNode = head
    let fast: ListNode = head
    while (slow){
        if (fast === null) { //偶数
            break
        } else if (fast.next === null){ //奇数
            slow = slow.next
            break
        }
        slow = slow.next
        fast = fast.next.next
    }
    let p: ListNode = head // 比较p和slow
    slow = reverse(slow)
    while (slow) {
        if (slow.val !== p.val){
            return false
        }
        slow = slow.next
        p = p.next
    }
    return true
};

其他类型的题目

代表性的有合并链表,删除链表重复元素,相交链表,两两交换链表节点,K个一组反转链表,这些大多数都可以去用到递归去做,这里不多赘述,这里主要以几个比较经典的案例来分析一些常见的链表类题型,如果有写错的地方请多多包涵~

你可能感兴趣的:([typescript] 链表类算法题型与方法总结)