这一次,学懂双指针

821 阅读2分钟

我正在参加「掘金·启航计划」

什么是双指针

双指针算法是指在数组中使用两个指针,从数组相同方向或者相反方向遍历数组,从而达到某种目的。

算法主要利用了数组有序的特点,根据遍历方向不同,可以分为左右指针和快慢指针(滑动窗口)两种类型。

这两种算法思想都可以优化程序的时间和空间复杂度,常用于数组、链表等数据结构的处理,本文主要介绍双指针在数组中的应用。

这里的指针,并非专指C中指针的概念,而是指索引,游标或指针,可迭代对象等

分类

左右指针

左右指针是指在数组中使用两个指针,分别指向数组的第一个元素和最后一个元素,根据题目的要求,通过移动指针的位置到中间,直到相遇,从而解决问题。

题目特点

每个循环,仅看数组中的2个元素,双指针意在找到符合条件的2个元素,求出对应的结果。

伪代码模板如下:

function fn (list) {
  var left = 0;
  var right = list.length - 1;

  //遍历数组
  while (left <= right) {
    left++;
    // 一些条件判断 和处理
    ... ...
    right--;
  }
}

上手试试

344. 反转字符串

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。

不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。

示例 1:

输入:s = ["h","e","l","l","o"]
输出:["o","l","l","e","h"]

示例 2:

输入:s = ["H","a","n","n","a","h"]
输出:["h","a","n","n","a","H"]

题解

var reverseString = function(s) {
  let left = 0;
  let right = s.length - 1;
  while (left <= right) {
    [s[left], s[right]] = [s[right], s[left]];
    left++;
    right--;
  }
  return s;
};

经典例题

167. 两数之和 II - 输入有序数组

15. 三数之和

5. 最长回文子串

快慢指针(滑动窗口)

快慢指针,也叫滑动窗口,它的思想是使用两个指针同时从同一个方向遍历某个数据结构,其中一个指针的移动速度较快,称为“快指针”,另一个指针的移动速度较慢,称为“慢指针”。

利用这种不同的移动速度,可以帮助我们解决一些复杂的问题。

通常情况下都是解决字符串匹配及字串问题,通过将字符串转换为数组,然后使用快慢指针操作,得到解。

题目特点

在每次循环中得到一个子区间,由两个指针框起来,通过判断区间是否满足条件,得到最优解。

伪代码模板如下:

function fn (list) {
  var left = 0;
  var right = 0;

  //遍历数组
  while (right < list.length) {
    // 一些条件判断 和 处理
    ... ...
    right++;
    // 一些条件判断 和 处理
    ... ...
    left++;
  }
}

上手试试

3. 无重复字符的最长子串

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:

输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:

输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:

输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

题解

var lengthOfLongestSubstring = function(str) {
  let n = str.length
  // 滑动窗口为s[left...right]
  let left = 0
  let right = -1
  let freqMap = {} // 记录当前子串中下标对应的出现频率
  let max = 0 // 找到的满足条件子串的最长长度

  while (left < n) {
    let nextLetter = str[right + 1]
    if (!freqMap[nextLetter] && nextLetter !== undefined) {
      freqMap[nextLetter] = 1
      right++
    } else {
      freqMap[str[left]] = 0
      left++
    }
    max = Math.max(max, right - left + 1)
  }

  return max
};

经典例题

  • 找到符合条件的子区间

209. 长度最小的子数组

438. 找到字符串中所有字母异位词

567. 字符串的排列

  • 子区间计数问题

795. 区间子数组个数

992. K 个不同整数的子数组 🏆

713. 乘积小于 K 的子数组

参考资料

双指针专题

双指针算法入门