链表专题一

309 阅读4分钟

这是我参与2022首次更文挑战的第2天,活动详情查看:2022首次更文挑战 | 创作学习持续成长,夺宝闯关赢大奖 - 掘金 (juejin.cn)

题目链接

  1. 环形链表 leetcode-cn.com/problems/li…
  2. 环形链表II leetcode-cn.com/problems/li…
  3. 快乐数 leetcode-cn.com/problems/ha…
  4. 反转链表 leetcode-cn.com/problems/re…
  5. 反转链表II leetcode-cn.com/problems/re…

基础补充

链表折磨专题零-js链表函数实现(leetcode) - 掘金 (juejin.cn)

题解及分析

环形链表

给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
如果链表中存在环,则返回 true 。 否则,返回 false 。

思路:

  • 声明指向头部的两个指针
  • 其中一个每次走1步,另一个每次走2步
  • 重合的时候即为有环 (推到参考题目总结)
var hasCycle = function(head) {
    if(!head) return false
    let slow = head
    let fast = head
    /**
     * 快慢指针法
     * 一直循环到快慢指针重合
     */
    do {
        if(!fast || !fast.next) return false
        slow = slow.next
        fast = fast.next.next
    }while(!(slow == fast))
    return true
};

(所以其实pos有什么用我是没看出来...求指点)

环形链表II

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改链表。

和上一题相比,多了“不能修改链表”这个条件,同时需要知道入环点

思路: 接上一题的思路

  • 在重合点申明一个变量cur,该变量指向链表头部
  • 循环cur和慢指针,知道两者相遇即是入环点 (推到参考题目总结)
let detectCycle = head => {
     if(head === null) return head
     let slow = head
     let fast = head
     while(fast !== null) {
         slow = slow.next
         if(fast.next !== null) {
             fast = fast.next.next
         } else {
             return null
         }
         if(fast === slow) {
            /**
             * 找到快慢指针重合的点之后,
             * 利用另外一个变量来寻找入环点
             */
             let ptr = head
             while(ptr !== slow) {
                 ptr = ptr.next
                 slow = slow.next
             }
             return ptr
         }
     }
    return null
 }

快乐数

编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」定义为:
对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
如果 可以变为  1,那么这个数就是快乐数。
如果 n 是快乐数就返回 true ;不是,则返回 false 。

这是一道延伸题目,隐蔽的点在于,通过对二次方的举例抽象之后发现能套入链表环的思路

let getNext = n => {
    /**
     * 返回两个位数的平方和
     */
     return n.toString().split('').map(i => i **2).reduce((a, b) => a+b)
}
var isHappy = function(n) {
    let slow = n
    let fast = getNext(n)
    while(fast !== 1 && fast !== slow) {
        slow = getNext(slow)
        fast = getNext(getNext(fast))
    }
    return fast === 1
};

反转链表

给你单链表的头节点head,请你反转链表,并返回反转后的链表。

思路一:迭代

  • 循环整个链表,将值交换反转
var reverseList = function(head) {
    if(!head) return null
    let pre = null
    let cur = head
    while (cur) {
        // 将a,b,c调整为c,a,b
        let next = cur.next
        cur.next = pre
        pre = cur
        cur = next
        // 解构赋值
        // [cur.next, pre, cur] = [pre, cur, cur.next]
    }
    return pre
};

思路二:递归

var reverseList = function(head) {
    if(head == null || head.next == null) {
        return head
    }
    const newHead = reverseList(head.next)
    head.next.next = head
    head.next = null
    return newHead
};

反转链表II

给你单链表的头指针head和两个整数left和right,其中left<=right。请你反转从位置left到位置right的链表节点,返回反转后的链表 。

和上一题相比,多了个限制条件,需要在某个区间内反转 那么思路其实不变,只是需要先找出需要反转的链表区域

var reverseBetween = function(head, left, right) {
    // 设置 dummyNode 是这一类问题的一般做法
    const dummy_node = new ListNode(-1);
    dummy_node.next = head;
    let pre = dummy_node;
    // 遍历找第一个节点
    for (let i = 0; i < left - 1; ++i) {
        pre = pre.next;
    }
    // 找到第一个节点后,反序遍历反转链表
    let cur = pre.next;
    for (let i = 0; i < right - left; ++i) {
        const next = cur.next;
        cur.next = next.next;
        next.next = pre.next;
        pre.next = next;
    }
    return dummy_node.next;
};

// 第二种写法,道理相同
var reverseBetween = (head, left, right) => {
    if(!head) return null
    let ret = new ListNode(-1, head)
    let pre = ret
    let cnt = right - left +1
    while (--left) {
        pre = pre.next
    }
    pre.next = reverse(pre.next, cnt)
    return ret.next
}
var reverse = (head, n) => {
    let pre = null
    let cur = head
    while (n--) {
        [cur.next, pre, cur] = [pre, cur, cur.next]
    }
     head.next = cur
     return pre
}

题目总结

  • 1,2,3基本上涉及到环,环形问题中一般,循环是最基础的写法,但相对性能较差,可以考虑用龟兔赛跑算法

    • 龟兔赛跑思路的核心在于,设置a为起点到入环点的长度,b为入环点到快慢相遇点的长度,c为相遇点到入环点的长度,经过数学处理后,有了 a=c+(n-1)(b+c)a=c+(n−1)(b+c) 的等量关系,换言之,从相遇点到入环点的距离加上 n-1 圈的环长,恰好等于从链表头部到入环点的距离。借用leetcode的图表示:

    环形链表.png

  • 4,5题涉及到链表反转问题,这类题目大致上都通过对节点进行重新排序来对链表的顺序进行反转

上期文章

[链表折磨专题零-js链表函数实现(leetcode) - 掘金 (juejin.cn)]