Skip to content

滑动窗口

统计信息:字数 5165 阅读11分钟

参考链接:https://blog.csdn.net/summer2day/article/details/89853737

链接使用 C++ 实现,现在改成 JS 实现

1、基础:双端队列:可以从前面或者后面插入或者删除元素。队列内部的元素可以单调递增或者单调递减(特殊的双端队列)。那么直接可以获取当前队列的最大值和最小值。因为 JS 数组中可以直接操作首尾元素,那么天然就是双端队列。然后只需要在增加减少的时候,重新排列数组的内部元素的位置。

2、滑动窗口:每次滑动后,当队列内部元素大于允许的个数时,就把首个元素移除。把小于新加入队列的全部值移除。

三个例子:滑动窗口的最大值、无重复元素的最长子串、长度最小的子数组

滑动窗口的最大值(239)

// 看一下还能怎么优化
// 用空间换时间,多使用内存,少使用循环!
var maxSlidingWindow = function(nums, k) {
  // 处理特殊情况
  if (k === 1) {
    return nums;
  }
  const len = nums.length;
  if (len === 1) {
    return nums[0];
  }

  // list 是单调递减的双端队列
  let list = [];
  list[0] = 0;

  // 初始化前面几个值
  for (let i = 1; i < k; i++) {
    let item = nums[i];
    let listLen = list.length;
    // 如果当前的值小于最后一个的值,直接插入到队列最后
    if (nums[list[listLen - 1]] > item) {
      list.push(i);
    }
    // 否则,找到合适的位置,插入队列中
    // 这个很消耗性能,如果 K = 30000 那么插入操作是 N 的平方,未来可以用二分法优化
    // 这里应该使用双端队列优化,现在直接在数组中插入性能很差
    else {
      for (let j = 0; j < listLen; j++) {
        if (nums[list[j]] <= item) {
          list.splice(j, listLen - j, i);
          break;
        }
      }
    }
  }
  // 现在已经把开始的K个元素放入了队列中,然后开始遍历剩下的数字,然后获取最大值
  let res = [];
  res[0] = nums[list[0]];

  for (let i = k; i < len; i++) {
    let item = nums[i];
    let listLen = list.length;
    // 如果新增的值小于最小的,那么直接插入最后
    if (nums[list[listLen - 1]] > item) {
      list.push(i);
    } else {
      for (let j = 0; j < listLen; j++) {
        if (nums[list[j]] < item) {
          list.splice(j, listLen - j, i);
          break;
        }
      }
    }
    // 把距离大于K的都删除掉
    list = list.filter((item) => {
      return i - item < k;
    });
    res.push(nums[list[0]]);
  }
  return res;
};
// 求字符串中最长的无重复字符的子字符串

// 思路:创建一个字典,存放当前字母出现的位置;创建一个 start 存放无重复子串的位置
// 循环字符串,然后判断字典中是否出现。如果出现,那么改变 start,同时更新字典中出现的位置
// 每次计算当前值和开始值的差,然后得出当前子串的长度
// 最后计算字符串的长度减去开始的长度,看一下最大值
let fn = (str) => {
  const len = str.length;
  if (len === 1) {
    return 1;
  } 
  let dict = {};
  let start = 0;
  dict[str[0]] = 0;
  let max = 1;
  // 开始从第二个判断
  for (let i = 1; i < len; i++) {
    // 存在相同的元素(更新开始的位置)
    if (dict[str[i]] > -1) {
      start = dict[str[i]] + 1;
      dict[str[i]] = i;
    }
    // 不存在相同的元素
    else {
      dict[str[i]] = i;
    }
    // 每次循环,计算最长的索引,长度是索引的差值 + 1
    let currentMax = i - start + 1;
    if (currentMax > max) {
      max = currentMax;
    }
  }
  return max;
};

// 输入: “abcabcbb”
// 输出: 3
// 解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。

// 输入: “bbbbb”
// 输出: 1
// 解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。

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

console.log(fn('abcabcbb'), fn('bbbbb'), fn('pwwkew'));

长度最小的子数组(209题目)

/**
 * @param {number} s
 * @param {number[]} nums
 * @return {number}
 */
 var minSubArrayLen = function(s, nums) {
  const len = nums.length;
  let left = 0;
  let right = 0;
  let sum = nums[0];
  while (sum < s) {
      right++;
      sum += nums[right];
      // 全部的和小于s, 那么直接返回0
      if (right === len) {
          return 0;
      }
  }
  let minLen = right - left + 1;
  // 开始遍历每一种情况
  while (left <= right && right < len) {
      if (sum > s) {
          sum -= nums[left];
          left++;
      } else {
          right++;
          if (right === len) {
              break;
          }
          sum += nums[right];
      }
      if (sum >= s) {
          let currentLen = right - left + 1;
          if (minLen > currentLen) {
              minLen = currentLen;
          }
      }
  }
  return minLen;
};

Last update: November 9, 2024