「前端练算法」2道leetcode带你走进滑动窗口

122 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情 >>

一、基础知识

  1. 为什么我们要用它:

    • 针对特定问题,减少复杂度,从两次循环遍历优化成一次循环遍历。
  1. 什么是窗口:

    1. 连续的子项,重点是连续。
    2. 滑动窗口算法会用到左右两个指针,中间的部分就是窗口。窗口可以伸缩、扩张,也可以平移。
  1. 滑动的是什么:左右指针
  1. 捕捉到什么关键词我可以用滑动窗口?:正如1中所说,当察觉到题目中的判定条件是针对一个连续的区间的时候,可以考虑滑动窗口进行解法。
  1. 那滑动窗口到底是什么?

    1. 第一步

  1. 第二步

  2. 第三步

二、模板篇

综上所述,可以抽象出一个代码模板:

function swipe(arr) {
    let result = 0
    let left = 0
    let right = 1
    while (left < arr.length - 1) {
        while (right < arr.length + 1) {
            if (// 满足某种条件之后) {
                right++                
            } else {
                break
            }
        }
        left ++
        // 也可能不是+1,根据题目来定
        right = left + 1
    }
    return result
}

三、题目解析篇

这次要解的两个题目是:1004. 最大连续1的个数 III3. 无重复字符的最长子串,都是比较典型的题目。

题解里有许多优秀的解法,我的时间复杂度和空间复杂度都不是最棒的那一波,但是我认为是相对好理解的那一波,哈哈。

1004. 最大连续1的个数 III

给定一个二进制数组 nums 和一个整数 k,如果可以翻转最多 k 个 0 ,则返回 数组中连续 1 的最大个数 。

示例 1:

输入:nums = [1,1,1,0,0,0,1,1,1,1,0], K = 2

输出:6

解释:[1,1,1,0,0,1,1,1,1,1,1]

粗体数字从 0 翻转到 1,最长的子数组长度为 6。

示例 2:

输入:nums = [0,0,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1], K = 3

输出:10

解释:[0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1]

粗体数字从 0 翻转到 1,最长的子数组长度为 10。

提示:

1 <= nums.length <= 105

nums[i] 不是 0 就是 1

0 <= k <= nums.length

来源:力扣(LeetCode)

链接:leetcode.cn/problems/ma…

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

  • 先读题,需要的是“数组中连续1的最大个数”,那么可以考虑滑动算法的解法。
       /**
        * @param {number[]} nums
        * @param {number} k
        * @return {number}
        */
       var longestOnes = function(nums, k) {
           // 边界条件容错
           if (!nums.length) return 0
           // 简化边界条件计算量 start,但也增加了耗时
           const initZeroList = nums.filter(num => num === 0)
           if (initZeroList.length <= k) return nums.length
           // 简化边界条件计算量 end
           let left = 0
           let right = 1
           let max = 0
           // 模板代码部分
           while (left < nums.length - 1) {
               while (right < nums.length + 1) {
                   // 简化,不符合条件的直接过掉
                   if (right - left <= max) {
                       right ++
                       continue
                   }
                   const zeroList = nums.slice(left, right).filter(num => num === 0)
                   if (zeroList.length <= k) {
                       max = right - left
                       right ++
                   } else {
                       break
                   }
               }
               left ++
               right = left + max
           }
           return max
       };

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

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

示例 1:

输入: s = "abcabcbb"

输出: 3 

解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2:

输入: s = "bbbbb"

输出: 1

解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例 3:

输入: s = "pwwkew"

输出: 3

解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。

  请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

提示:

0 <= s.length <= 5 * 104

s 由英文字母、数字、符号和空格组成

来源:力扣(LeetCode)

链接:leetcode.cn/problems/lo…

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

同样画关键词,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串,所以继续考虑滑动窗口。

/**
 * @param {string} s
 * @return {number}
 */
var lengthOfLongestSubstring = function (s) {
  if (!s) return 0;
  if (s.length === 1) return 1;
  // 通过hashmap来判断是否有重复
  function hasRepeat(arr) {
    const hash = {};
    for (let i = 0; i < arr.length; i++) {
      if (hash[arr[i]]) {
        return true;
      }
      hash[arr[i]] = true;
    }
    return false;
  }
  // 模板代码开始
  let max = 0;
  const sArr = s.split("");
  let left = 0;
  let right = 1;
  while (left < sArr.length - 1) {
    while (right < sArr.length + 1) {
      if (right - left < max) {
        right++;
        continue;
      }
      if (!hasRepeat(sArr.slice(left, right))) {
        max = right - left;
        right++;
      } else {
        break;
      }
    }
    left++;
    right = left + max;
  }
  return max;
};

这是我的小白学算法系列第一篇