心痛之窗:滑动窗口算法解爱与愁的心痛(洛谷P1614)

题目背景与情感共鸣

这道名为"爱与愁的心痛"的题目,巧妙地将情感主题与算法问题相结合。题目背景引用了《爱与愁的故事》和《我为歌狂》中的情节,营造出一种青春伤感的情感氛围。而算法核心则是寻找连续子数组的最小和,这种"心痛"的量化表达让人印象深刻。

问题分析

题目要求

给定一个包含n个正整数的序列(刺痛值),要求找出连续m个数字和的最小值

输入输出示例

cpp 复制代码
输入:
8 3
1
4
7
3
1
2
4
3

输出:
6

解释:连续3个刺痛值的最小和是1+2+3=6(对应第5、6、7个数字)

解题思路详解

核心算法:滑动窗口(Sliding Window)

滑动窗口算法是解决这类连续子数组问题的经典方法。其核心思想是维护一个固定大小的窗口,在数组上滑动,避免重复计算。

算法步骤

  1. 初始化窗口:计算前m个数字的和作为初始窗口和
  2. 滑动窗口:每次向右移动一位,减去离开窗口的数字,加上新进入窗口的数字
  3. 更新最小值:在滑动过程中记录遇到的最小和
  4. 边界处理:处理m=0的特殊情况

C++代码实现

基础版本:清晰易懂

cpp 复制代码
#include <iostream>
#include <vector>
#include <climits> // 用于INT_MAX
using namespace std;

int main() {
    int n, m;
    cin >> n >> m;
    
    vector<int> pain(n);
    for (int i = 0; i < n; i++) {
        cin >> pain[i];
    }
    
    // 处理m=0的特殊情况
    if (m == 0) {
        cout << 0 << endl;
        return 0;
    }
    
    // 初始化第一个窗口的和
    int current_sum = 0;
    for (int i = 0; i < m; i++) {
        current_sum += pain[i];
    }
    
    int min_sum = current_sum;
    
    // 滑动窗口
    for (int i = m; i < n; i++) {
        current_sum = current_sum - pain[i - m] + pain[i];
        if (current_sum < min_sum) {
            min_sum = current_sum;
        }
    }
    
    cout << min_sum << endl;
    return 0;
}

优化版本:处理边界更完善

cpp 复制代码
#include <iostream>
#include <vector>
#include <climits>
using namespace std;

int main() {
    int n, m;
    cin >> n >> m;
    
    // 边界情况处理
    if (n == 0 || m == 0) {
        cout << 0 << endl;
        return 0;
    }
    
    vector<int> pain(n);
    for (int i = 0; i < n; i++) {
        cin >> pain[i];
    }
    
    // 如果m大于n,实际上只能取n个连续数字
    int window_size = min(m, n);
    int current_sum = 0;
    
    // 计算第一个窗口
    for (int i = 0; i < window_size; i++) {
        current_sum += pain[i];
    }
    
    int min_sum = current_sum;
    
    // 滑动窗口处理剩余部分
    for (int i = window_size; i < n; i++) {
        current_sum += pain[i] - pain[i - window_size];
        min_sum = min(min_sum, current_sum);
    }
    
    cout << min_sum << endl;
    return 0;
}

单次遍历版本:极致简洁

cpp 复制代码
#include <iostream>
#include <vector>
#include <climits>
using namespace std;

int main() {
    int n, m;
    cin >> n >> m;
    
    vector<int> pain(n);
    for (int i = 0; i < n; i++) {
        cin >> pain[i];
    }
    
    if (m == 0) {
        cout << 0 << endl;
        return 0;
    }
    
    int current_sum = 0, min_sum = INT_MAX;
    
    for (int i = 0; i < n; i++) {
        current_sum += pain[i];
        
        // 当窗口大小达到m时开始滑动
        if (i >= m - 1) {
            min_sum = min(min_sum, current_sum);
            current_sum -= pain[i - m + 1];
        }
    }
    
    cout << min_sum << endl;
    return 0;
}

关键知识点深度解析

1. 滑动窗口算法(⭐⭐⭐⭐⭐)

  • 核心思想:维护固定大小的窗口,避免重复计算
  • 时间复杂度:O(n),只需遍历数组一次
  • 空间复杂度:O(1),只使用常数个额外变量

2. 边界条件处理(⭐⭐⭐⭐)

  • m=0情况:连续0个数字的和为0
  • m>n情况:实际窗口大小应为min(m,n)
  • 数组为空:n=0时的特殊处理

3. 算法优化技巧(⭐⭐⭐)

  • 提前终止:如果当前和已经很大,可以提前判断
  • 最小值更新:使用min函数简化代码
  • 变量复用:重复使用current_sum变量

测试用例与验证

标准测试用例

cpp 复制代码
// 测试用例1:题目样例
输入:8 3
      1 4 7 3 1 2 4 3
输出:6

// 测试用例2:边界情况
输入:5 0
      1 2 3 4 5
输出:0

// 测试用例3:全相同数字
输入:4 2
      5 5 5 5
输出:10

// 测试用例4:递增序列
输入:5 3
      1 2 3 4 5
输出:6

性能测试

根据数据规模分析:

  • n ≤ 20:任何算法都能轻松处理
  • n ≤ 1000:滑动窗口算法优势明显
  • n ≤ 3000:滑动窗口仍然高效(3000次操作)

常见错误与解决方法

错误1:窗口大小处理不当

cpp 复制代码
// 错误:未处理m>n的情况
for (int i = 0; i < m; i++) { // 如果m>n会越界
    current_sum += pain[i];
}

解决

cpp 复制代码
int window_size = min(m, n);
for (int i = 0; i < window_size; i++) {
    current_sum += pain[i];
}

错误2:索引计算错误

cpp 复制代码
// 错误:索引计算偏差
current_sum = current_sum - pain[i - m] + pain[i];
// 当i=m时,i-m=0,正确

错误3:最小值初始化错误

cpp 复制代码
// 错误:初始化为0
int min_sum = 0; // 如果所有和都大于0,会得到错误结果

// 正确:初始化为极大值
int min_sum = INT_MAX;

竞赛技巧总结

  1. 识别滑动窗口模式:当问题涉及连续子数组时考虑此算法
  2. 边界测试优先:先处理m=0, n=0等边界情况
  3. 代码简洁性:使用标准库函数简化代码(如min, max)
  4. 提前优化:根据数据范围选择合适算法

实际应用拓展

滑动窗口算法在以下场景有广泛应用:

1. 数据流处理

cpp 复制代码
// 实时数据流中的滑动平均值
class MovingAverage {
private:
    queue<int> window;
    int size;
    double sum;
public:
    MovingAverage(int size) : size(size), sum(0.0) {}
    
    double next(int val) {
        if (window.size() == size) {
            sum -= window.front();
            window.pop();
        }
        window.push(val);
        sum += val;
        return sum / window.size();
    }
};

2. 字符串处理

cpp 复制代码
// 最长无重复字符子串
int lengthOfLongestSubstring(string s) {
    vector<int> charIndex(256, -1);
    int maxLength = 0, left = 0;
    
    for (int right = 0; right < s.length(); right++) {
        if (charIndex[s[right]] >= left) {
            left = charIndex[s[right]] + 1;
        }
        charIndex[s[right]] = right;
        maxLength = max(maxLength, right - left + 1);
    }
    return maxLength;
}

总结与提升建议

通过这道"爱与愁的心痛",我们掌握了:

  1. 滑动窗口核心思想:固定窗口大小的连续子数组处理
  2. 算法优化技巧:从O(n²)暴力解法优化到O(n)高效算法
  3. 边界处理能力:处理各种极端输入情况

进一步提升建议

  • 练习滑动窗口的变种问题(如可变窗口大小)
  • 学习双指针技术的其他应用
  • 掌握更多子数组相关问题的解法

"算法如人生,有时候我们需要在连续的经历中寻找最小的'心痛',而滑动窗口教会我们高效地审视每一个片段。"

这道题目不仅考察了算法能力,更通过情感主题让编程变得生动有趣。滑动窗口作为一种基础而强大的算法思想,值得深入理解和掌握。

相关推荐
froginwe119 小时前
C# 判断语句详解
开发语言
图灵信徒9 小时前
2024南京icpc区域赛详解与难点解释
c++·acm·icpc·算法竞赛
咖啡啡不加糖9 小时前
贪心算法详解与应用
java·后端·算法·贪心算法
YxVoyager10 小时前
Qt C++ :XML文件处理工具 <QXml>模块
xml·c++·qt
無斜10 小时前
【LabVIEW实用开发】--- LabVIEW调用python脚本
开发语言·python·labview
一只鱼^_10 小时前
力扣第470场周赛
数据结构·c++·算法·leetcode·深度优先·动态规划·启发式算法
CUMT_DJ14 小时前
matlab计算算法的运行时间
开发语言·算法·matlab
greentea_201315 小时前
Codeforces Round 65 A. Way Too Long Words(71)
c++
Overboom17 小时前
[C++] --- 常用设计模式
开发语言·c++·设计模式