算法--滑动窗口算法不适用于有负数原因

发布于:2025-09-02 ⋅ 阅读:(15) ⋅ 点赞:(0)

滑动窗口算法不适用于有负数的情况的原因

滑动窗口算法在处理包含负数的数组时会遇到问题,主要原因在于其基于的假设和机制在负数存在时不再成立。让我详细解释一下:

基本滑动窗口算法的工作原理

标准的滑动窗口算法通常用于解决"找到满足某种条件的最短/最长连续子数组"的问题,例如"和为K的最短子数组"。

它的基本思路是:

  1. 使用两个指针(left和right)表示窗口的左右边界
  2. 移动右指针扩大窗口,直到满足条件
  3. 移动左指针缩小窗口,寻找最优解

为什么负数会导致问题

1. 单调性假设被打破

滑动窗口算法基于一个关键假设:当窗口扩大时,窗口内的和会增加;当窗口缩小时,窗口内的和会减少

当数组包含负数时,这个假设不再成立:

  • 扩大窗口(右指针右移)时,如果遇到负数,窗口和可能反而减少
  • 缩小窗口(左指针右移)时,如果移除的是负数,窗口和可能反而增加
// 示例:数组 [2, -1, 2],寻找和为3的子数组
// 初始窗口: [2] → 和为2
// 扩大窗口: [2, -1] → 和为1(反而减少了)
// 继续扩大: [2, -1, 2] → 和为3(找到了)

2. 无法确定窗口调整方向

由于窗口和的变化不再单调,算法无法确定应该扩大还是缩小窗口来接近目标值:

// 寻找和为0的子数组
vector<int> nums = {1, -1, 2, -2, 3};
// 当前窗口[1] → 和为1 > 0,应该缩小窗口吗?
// 但下一个元素是-1,扩大窗口后[1,-1]和变为0,正是我们要找的

3. 可能错过最优解

滑动窗口算法可能会因为非单调性而错过某些解:

// 数组: [5, -3, 2, 1, -4, 6],寻找和为0的子数组
// 可能的解: [-3, 2, 1] 或 [-4, 6, -2](如果存在-2)
// 但滑动窗口可能会跳过这些解,因为:
// [5] → 和=5 > 0 → 扩大窗口
// [5,-3] → 和=2 > 0 → 继续扩大
// [5,-3,2] → 和=4 > 0 → 继续扩大
// [5,-3,2,1] → 和=5 > 0 → 继续扩大
// [5,-3,2,1,-4] → 和=1 > 0 → 继续扩大
// [5,-3,2,1,-4,6] → 和=7 > 0
// 算法错过了[-3,2,1]这个解

替代方案

对于包含负数的数组,通常需要使用其他方法:

1. 前缀和 + 哈希表

这是处理包含负数的子数组和问题的常用方法:

cpp

#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;

int minSubArrayLen(int target, vector<int>& nums) {
    unordered_map<int, int> prefixSum; // 存储前缀和和对应的索引
    prefixSum[0] = -1; // 初始前缀和为0,索引为-1
    
    int sum = 0, minLen = INT_MAX;
    for (int i = 0; i < nums.size(); i++) {
        sum += nums[i]; // 计算当前前缀和
        
        // 查找是否存在prefixSum[sum] - prefixSum[?] = target
        // 即查找是否存在prefixSum[sum - target]
        if (prefixSum.find(sum - target) != prefixSum.end()) {
            minLen = min(minLen, i - prefixSum[sum - target]);
        }
        
        // 只保留最早出现的前缀和,以确保得到最长的子数组
        if (prefixSum.find(sum) == prefixSum.end()) {
            prefixSum[sum] = i;
        }
    }
    
    return minLen == INT_MAX ? 0 : minLen;
}

int main() {
    vector<int> nums = {2, -1, 2};
    int target = 3;
    cout << "最短子数组长度: " << minSubArrayLen(target, nums) << endl;
    return 0;
}

2. 动态规划

对于某些问题,动态规划可能是更好的选择:

#include <iostream>
#include <vector>
#include <climits>
using namespace std;

int maxSubArray(vector<int>& nums) {
    int maxSum = INT_MIN;
    int currentSum = 0;
    
    for (int num : nums) {
        currentSum = max(num, currentSum + num);
        maxSum = max(maxSum, currentSum);
    }
    
    return maxSum;
}

int main() {
    vector<int> nums = {-2, 1, -3, 4, -1, 2, 1, -5, 4};
    cout << "最大子数组和: " << maxSubArray(nums) << endl;
    return 0;
}

总结

滑动窗口算法不适用于包含负数的情况,主要是因为:

  1. 负数打破了窗口和变化的单调性假设
  2. 算法无法确定应该扩大还是缩小窗口
  3. 可能错过最优解