定长滑动窗口

# 定长滑动窗口

定长滑动窗口,是窗口长度固定,依次滑动。

# 图示

# 模板

定长类滑动窗口,例如窗口长度为k,第一步先移动k个元素,第二步再固定移动到结束,两步一般分开写。当然也可以写到一个循环体内,但是会加if判断,因此一般分开写,效率会更高点。

var fixedLength = function (nums, k) {
  // 1. 先计算前k个元素
  let window = 0;
  for (let i = 0; i < k; i++) {
    window += nums[i];
  }

  // 2. 移动窗口到数组结尾
  let max = window;
  for (let i = k; i < nums.length; i++) {
    // 加入新元素,移除最左边元素,移动窗口
    window = window + nums[i] - nums[i - k];
    
    max = Math.max(max, window);

    right++;
  }
  return max;
};

# 例题

# LC 643. 子数组最大平均数 I

Q:给你一个由 n 个元素组成的整数数组 nums 和一个整数 k 。请你找出平均数最大且 长度为 k 的连续子数组,并输出该最大平均数。

A:这个问题是经典的定长滑动窗口入门题目。

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number}
 */
var findMaxAverage = function (nums, k) {
    // sliding window
    let sum = 0;
    let max = 0;
    // 1. 计算前k个元素的和
    for (let i = 0; i < k; i++) {
        sum += nums[i];
    }

    // 2. 移动滑动窗口,记录最大的窗口之和
    max = sum;
    for (let i = k; i < nums.length; i++) {
        sum = sum + nums[i] - nums[i - k];
        max = Math.max(max, sum);
    }
    return max / k;
};

# LC 1423. 可获得的最大点数

Q:

几张卡牌 排成一行,每张卡牌都有一个对应的点数。点数由整数数组 cardPoints 给出。

每次行动,你可以从行的开头或者末尾拿一张卡牌,最终你必须正好拿 k 张卡牌。

你的点数就是你拿到手中的所有卡牌的点数之和。

给你一个整数数组 cardPoints 和整数 k,请你返回可以获得的最大点数。

A:这个题目也是定长类问题,特别点在于思维的转化。由于只能从开头或末尾拿,因此这里有两个解法:正向思维和反向思维

  • 正向思维,滑动区间在 [n-k, n-1] - [0, k-1],因此可以先移动k个元素,然后反向滑动,核心点处理好窗口左指针
  • 逆向思维,当两边最大时,中间必然最小,因此问题可转化为求中间最小点数,则两边最大点数 = 总的点数 - 中间最小点数。该解法需要仔细体会,很多问题都在于转化,问题可能做了修饰,因此先根绝模式识别找到基础解法,然后对问题做层层转化。
/**
 * @param {number[]} cardPoints
 * @param {number} k
 * @return {number}
 */
var maxScore = function (cardPoints, k) {
    // 正向思维, [n-k, n-1] - [0, k-1]
    {
        // sliding window
        let window = 0;
        // 先移动k步
        for (let i = 0; i < k; i++) {
            window += cardPoints[i];
        }

        let max = window;
        // 然后左移
        let left = cardPoints.length - 1;
        let right = k - 1;
        while (right >= 0) {
            window = window + cardPoints[left] - cardPoints[right];
            max = Math.max(max, window);
            left--;
            right--;
        }
        return max;
    }

    // 逆向思维,两边最大 = 总点数 - 中间最小
    {
        // sliding window
        // 逆向思维,求 n - k 个窗口内 和最小
        const n = cardPoints.length;
        let sum = 0;
        let total = 0;
        // 先移动k步
        for (let i = 0; i < n - k; i++) {
            sum += cardPoints[i];
        }
        total = sum;

        // 求中间最小
        let min = sum;
        for (let i = n - k; i < n; i++) {
            sum = sum + cardPoints[i] - cardPoints[i - (n - k)];
            min = Math.min(min, sum);

            total += cardPoints[i];
        }

        // 两边最大 = 总点数 - 中间最小
        return total - min;
    }
};

# 小结

定长类滑动窗口是基础形式,需要注意以下几点:

  • 注意模板的写法,一般分两步,先移动k步,再移动到最后,避免循环体内判断语句
  • 部分题目会对题目做修饰,对问题做掩盖,因此需要做好分析,对问题层层剖析,最后做转化。

定长类滑动窗口其实是不定长类的一个子集,但是由于其固定长度的特性,分为两步走,效率会更高。

# 附录

# 定长类题目

Last Updated: 6/23/2022, 5:33:14 PM