「算法修炼」 Leetcode - 双指针 - 快慢指针

495 阅读6分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

快慢指针

两个指针从同一侧开始遍历数组,将这两个指针分别定义为快指针(fast)慢指针(slow),两个指针将以不同的策略(速度)移动,直到两个指针的值相等(或其他条件)为止,例如快指针fast是慢指针slow的两倍速度。

快慢指针主要用于解决链表中的问题:

  • 判断链表是否有环:快慢指针从头节点出发,如果链表中存在环,两个指针最终会在环中相遇。
  • 寻找链表的中点:快慢指针从头节点出发,每轮迭代中,快指针向前移动两个节点,慢指针向前移动一个节点,最终当快指针到达终点的时候,慢指针刚好在中间的节点。
  • 求链表中环的长度:从相遇后一个指针不动,另一个指针继续前进,直到再次相遇,可以算出走了多少步再次相遇。

快慢指针的相关题目

题型1 - 判断链表是否有环

  1. 题目

    141.环形链表 Easy:给定一个链表的头节点 head ,判断链表中是否有环。

  2. 解题思路

    使用快慢指针fastslow,从一侧出发,fastslow指针初始都指向head,循环链表,每次循环fast向前两步,slow向前走一步。如果没有环,则快指针fast会抵达终点,如果有环,那么快指针会追上慢指针。

  3. 代码

// @lc code=start
/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} head
 * @return {boolean}
 */
var hasCycle = function(head) {
    // 定义快慢指针,初始都指向头结点head
    let fast  = head
    let slow = head;
    // 当fast.next为null时说明fast走到了终点,没有环
    while (fast && fast.next){
        // 慢指针每次1步
        slow = slow.next;
        // 快指针每次2步 
        fast = fast.next.next;
        //快慢指针相遇,说明含有环
        if (slow == fast) {
            return true;
        }
    }
    return false
};
// @lc code=end
  1. 复杂度分析

    • 时间复杂度:O(n),其中 n 是链表中的节点数。

      当链表中不存在环时,快指针将先于慢指针到达链表尾部,链表中每个节点至多被访问两次。

      当链表中存在环时,每一轮移动后,快慢指针的距离将减小一。而初始距离为环的长度,因此至多移动 n 轮。

    • 空间复杂度:O(1),只使用了两个指针的额外空间。

题型2 - 寻找链表的中点

  1. 题目

    876.链表的中间结点Easy:给定一个头结点为 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。

  2. 解题思路

    使用快慢指针fastslow,从一侧出发,fastslow指针初始都指向head,每次fast向前两步,slow向前走一步。当快指针fast抵达终点时,慢指针刚好在中间的节点。

  3. 代码

// @lc code=start
/**
 * 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) {
  // 定义快慢指针, 同侧出发,初始为head
  let fast = head
  let slow = head
  // 快指针每次2步,慢指针每次1步
  // 快指针到达终点时,慢指针所在位置极为中点
  // 当链表节点为奇数时,快指针会恰好走到终点
  // 当链表节点为偶数时,快指针会走到终点前一个节点,
  // 但此时不满足fast.next === null, 会继续执行while,让slow走到第二个中间节点
  while (fast && fast.next) {
    fast = fast.next.next
    slow = slow.next    
  }
  return slow
};
// @lc code=end
  1. 复杂度分析

    • 时间复杂度:O(n),其中 n 是给定链表的结点数目。
    • 空间复杂度:O(1),只需要常数空间存放 slowfast 两个指针。

题型3 - 求链表中环的入口节点

  1. 题目

    142. 环形链表 II Medium:给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

  2. 解题思路

    • 判断是否有环:使用快慢指针fastslow,从一侧出发,fastslow指针初始都指向head,循环链表,每次循环fast向前两步,slow向前走一步。如果没有环,则快指针fast会抵达终点,如果有环,那么快指针会追上慢指针。
    • 有环则寻找入环点:当快指针与慢指针首次相遇时,设置一个新指针res,初始也指向链表头部。随后,它和慢指针slow 每次移动一个位置,它们会在入环点相遇。
  3. 思路解释

    • 为什么快指针fast和慢指针slow一定是在slow的第一圈相遇?
// 假设环长L
// slow进入环的时候,假设fast距离slow的长度为n , n <= L

// 第①种情况: n = L
// 当 n = L 时,说明 fast 此时就和 slow 已经相遇

// 第②种情况:n < L
// 当 n < L 时,fast 每次走两步,slow 每次走一步 ,所以每走一次,它们的距离会缩短1;
// 也就是需要n次,fast就会与slow相遇
// 走n次 slow 走了n步,而 n < L,

// 综上所述,slow 一定走不完一圈就会被追上
- 为什么新指针`res`会和慢指针`slow`在入环点相遇?
// 设环外部分长度为a,slow指针在环内走过的长度为b,环内剩余的长度为c。

// slow走过的路程为
slow = a + b

// fast走过的路程为 
fast = a + b + c + b

// fast 走过的路程是 slow 的2倍
fast === 2 * slow 

// 将fast和slow代入
a + b + c + b === 2 * (a + b)
//可得  
c === a 
//即 环外部分长度a === 环内slow走1圈剩余的长度c
  1. 代码
// @lc code=start
/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var detectCycle = function(head) {
    // 定义快慢指针,初始都指向头结点head
    let fast = head 
    let slow = head
    while (fast && fast.next) {
      // 快指针每次走2步
      fast = fast.next.next
      // 慢指针每次走1步
      slow = slow.next
      //快慢指针相遇,说明含有环
      if (slow == fast) {
        // 定义一个新指针res,初始指向头节点head
        let res = head 
        // 新指针res和慢指针slow会在入环点相遇
        while (res != slow) {
          res = res.next
          slow = slow.next
        }
        return res
      }
    }
    return null
};
// @lc code=end
  1. 复杂度分析

    • 时间复杂度:O(n),其中 n 为链表中节点的数目。在最初判断快慢指针是否相遇时,slow 指针走过的距离不会超过链表的总长度;随后寻找入环点时,走过的距离也不会超过链表的总长度。因此,总的执行时间为 O(n)+O(n)=O(n)
    • 空间复杂度:O(1)。只使用了 fastslowres 三个指针。