滑动窗口

106 阅读2分钟

滑动窗口

在解决连续数组类问题时,滑动窗口是一个很好的解法,下面以一个例子进行说明

长度最小的子数组

给定一个含有 n 个正整数的数组和一个正整数 target.

找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度. 如果不存在符合条件的子数组,返回 0 。

输入: target = 7, nums = [2,3,1,2,4,3]
输出: 2
解释: 子数组 [4,3] 是该条件下的长度最小的子数组。
  • 一开始,滑动窗口只有2一个元素,sum小于target,右端点不断向右扩展
  • 滑动窗口扩展到2,3,1,2时,sum大于target,此时左端点收缩,更新最小连续子数组长度
  • 收缩后,sum小于target,右端点向右扩展
  • ...
  • 直到滑动窗口元素只有4,3时,得到最小连续子数组长度
function minSubArrayLen(target: number, nums: number[]): number {
  let res: number = Infinity
  let left = 0,
    right = 0,
    sum = nums[left]
  while (left <= right && right < nums.length) {
    if (sum >= target) {
      res = Math.min(res, right - left + 1)
      sum -= nums[left]
      left++
    } else {
      right++
      sum += nums[right]
    }
  }
  if (left === 0 && right === nums.length) return 0
  return res
}

滑动窗口写法模板

function minSubArrayLen(target: number, nums: number[]): number {
  let res: number = Infinity
  let left = 0, sum = 0
  for (let right = 0; right < nums.length; right++) {
    // 更新右端点
    sum += nums[right]
    // 更新左端点
    while(sum >= target) {
      sum -= nums[left]
      res = Math.min(res, right - left + 1)
      left++
    }
  }
  if(res > nums.length) res = 0
  return res
}

乘积小于k的子数组

给你一个整数数组 nums 和一个整数 k ,请你返回子数组内所有元素的乘积严格小于 k 的连续子数组的数目。

输入: nums = [10,5,2,6], k = 100
输出: 8
解释: 8 个乘积小于 100 的子数组分别为:[10][5][2],、[6][10,5][5,2][2,6][5,2,6]。
需要注意的是 [10,5,2] 并不是乘积小于 100 的子数组。
function numSubarrayProductLessThanK(nums: number[], k: number): number {
  let res: number = 0,
    prod = 1,
    left = 0
  for (let right = 0; right < nums.length; right++) {
    if (k <= 1) return 0
    // 更新右端点
    prod *= nums[right]
    // 更新左端点
    while (prod >= k) {
      prod /= nums[left]
      left++
    }
    // left, left + 1, ..., right 相乘均小于k
    // 那么以right结尾的子数组个数为 right - left + 1
    res += right - left + 1
  }
  return res
}

无重复字符的最长子串

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

输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
function lengthOfLongestSubstring(s: string): number {
  if (s.length === 0) return 0
  let res = 0,
    left = 0,
    cache = new Map() // 记录出现过的字符个数
  for (let right = 0; right < s.length; right++) {
    const num = cache.get(s[right]) || 0
    // 更新右端点
    cache.set(s[right], num + 1)
    // 更新左端点
    while (cache.get(s[right]) > 1) {
      cache.set(s[left], cache.get(s[left]) - 1)
      left++
    }
    res = Math.max(res, right - left + 1)
  }
  return res
}