【算法对比】连续子区间的两种控制逻辑:Kadane和滑动窗口

0. 前言

在做数组相关的算法题时,连续子区间 是出现频率最高,也是花样最多的一个考点。

本文将通过两道算法题,深度对比处理连续子区间的两大方法:**针对最大和的 Kadane 算法 ** 与 针对最小区间长度的滑动窗口

1. 问题描述

1.1 岁晚可可塔

这道题其实就是求最大连续总和,数组元素有正有负。

1.2 内存块的最短分配

这道题其实就是求符合要求的最短连续长度。

现在有一排连续的内存块,每个内存块的大小不一,由一个正整数数组 nums 表示。现在需要分配一个 连续 的内存区域,使得该区域内所有内存块的大小总和 大于等于 给定的目标值 target

请找出满足条件的 最短连续子数组 的长度。如果不存在符合条件的子数组,返回 0。

输入:

  • 第一行:一个整数 target,目标内存大小。
  • 第二行:一个整数 n,代表内存块的总数量。
  • 第三行:n 个由空格分隔的正整数,每个正整数代表对应的每个内存块的大小。

输出:

  • 一个整数,代表满足条件的 最短连续子数组的长度

2. 算法逻辑对比

我用一个表格总结了一下 Kadane 算法和滑动窗口的算法逻辑,方便大家根据合适的场景进行选择:

3. 代码实现

3.1 岁晚可可塔代码

cpp 复制代码
#include <iostream>
#include <string>
#include <sstream>

using namespace std;

int main() {
    int n;
    cin >> n;

    string dummy;
    getline(cin, dummy);//拿掉n后面的换行符

    string line;
    getline(cin, line);
    stringstream ss(line);//将第二行转换成流,然后一个一个往出拿

    long max_sum = -2e7;//最终最大甜度,初始赋个极小值,防止第二行全为负数
    long curr_sum = 0;//当前最大甜度
    for(int i=0; i<n; i++)
    {
        long curr;
        ss >> curr;//从流里面拿出来一个

        //kadane算法核心
        //如果前面的加上当前的,和还不如当前的自身大,那么前面全是累赘,直接抛弃,以当前为新的开头
       curr_sum = max(curr, curr+curr_sum);

        //更新max
       max_sum = max(curr_sum , max_sum);
    }
    cout << max_sum;
    return 0;
}

有个需要注意的点,如果甜度全是负数,最大值应该也是负数。

这时,如果 max_sum 的初始值为 0,后面取 max 是就会得到 0,最终结果也就是 0,从而导致错误。

还有,在开头,读取 n 的值之后,用 getline 拿掉 n 后面的换行符很关键,否则就会导致后面本该读取第二行内容的 getline 读到了第一行的换行符,也会导致错误。

然后就是 for 循环中的 Kadane 算法,其大致思路如下:

对于数组中的每个元素 x,我们有两种选择:第一,如果前面和为正的话,把 x 加入前面的子数组。第二,如果前面和为负,就抛弃前面的子数组,从 x 本身重新开始一个新的子数组。

3.2 内存块的最短分配代码

cpp 复制代码
#include <iostream>
#include <string>
#include <sstream>
#include <vector>

using namespace std;

int main()
{
	int target, n;
	string dummy;

	cin >> target;
	getline(cin,dummy);//依旧拿掉换行符
	cin >> n;
	getline(cin, dummy);

	string line;
	getline(cin, line);
	stringstream ss(line);

	int left = 0;
	int min_len = n+1;//将最小长度初始化为一个较大的值
	int sum = 0;

	vector<int> nums;//动态数组,存放每个内存块的大小
	for(int i=0; i<n; i++)
	{
        //从流中逐个获取每个内存块大小,并存入数组
		int temp;
		ss >> temp;
		nums.push_back(temp);
	}

	for(int right = 0; right < n; right++)//right负责向右扩展
	{
		sum += nums[right];//每遍历到一个元素,就把它加到sum

		while(sum >= target)//每当sum已经达到target要求时,开始考虑收缩left,直到sum刚好再次小于target
		{
			min_len = min(min_len, right-left+1);//进入while循环,一定要先记录最小长度,否则会出错

			sum -= nums[left];
			left++;//左边界收缩
		}
	}
	if(min_len == n+1) cout << 0;
	else cout << min_len;
    return 0;
}

这段代码中唯一需要拆解的就是滑动窗口的运作逻辑,如下:

cpp 复制代码
while(sum >= target)//先检查现在的sum够不够
{
    //记录,此时sum确实是>=target的,这一刻的长度是满足要求的
	min_len = min(min_len, right-left+1);

    //尝试缩小,把左边的数减掉
	sum -= nums[left];
	//left向右挪
    left++;
    
    //回到最上面重新检查,不满足就跳出循环
}

关键点就在于我们在执行第 3 步减掉数字之前,已经先在第 2 步把当前的 min_len 存起来了,如果减掉 nums[left] 之后,sum 变得小于 target 了,下一次 while 循环的条件就会判定为 false,循环直接结束,而我们最后一次记录 min_len 的那个时刻,sum 依然是满足条件的。

4. 避坑指南

  • 为什么内存块那道题不能用 Kadane?
    • Kadane 是单指针,只能一值向前走,它只在乎 有没有变大,不会在满足 target 后,回过头去尝试缩短左边界。
  • 为什么可可塔不能用滑动窗口?
    • 滑动窗口依赖 单调性 ,也就是说右移 right 必然会让 sum 变大。如果数组里有负数,这个方法就没用了,窗口的伸缩会变得乱七八糟。

5. 算法模型选择

看到下面的场景时,可以根据上文讲的 Kadane 和滑动窗口的特征选择合适的模型:

  • 求连续一段的最大和?

    • 优先考虑 Kadane。
  • 求满足条件的连续最短或最长长度?

    • 优先考虑滑动窗口。
  • 数组里全是正数?

    • 滑动窗口。
  • 数组里正负数都有?

    • 必须用 Kadane。
相关推荐
꧁细听勿语情꧂2 小时前
合并两个有序表、判断链表的回文结构、相交链表、环的链表一和二
c语言·开发语言·数据结构·算法
结衣结衣.2 小时前
手把手教你实现文档搜索引擎
linux·c++·搜索引擎·开源·c++11
木井巳2 小时前
【递归算法】解数独
java·算法·leetcode·决策树·深度优先·剪枝
t***5442 小时前
如何在 Dev-C++ 中切换编译器
java·开发语言·c++
大肥羊学校懒羊羊2 小时前
完数与盈数的计算题解
数据结构·c++·算法
澈2073 小时前
构造函数与析构函数完全指南
开发语言·c++
阿Y加油吧3 小时前
算法实战笔记:LeetCode 31 下一个排列 & 287 寻找重复数
笔记·算法·leetcode
穿条秋裤到处跑3 小时前
每日一道leetcode(2026.04.24):距离原点最远的点
算法·leetcode·职场和发展
W23035765733 小时前
C++ 高并发线程池实战(二):动态缓存线程池 + 调用者运行拒绝策略完整版实现
开发语言·c++·缓存
wayz113 小时前
Day 13 编程实战:朴素贝叶斯与极端涨跌预警
人工智能·算法·机器学习