leetcode209 长度最小的子数组(C++)

leetcode209 长度最小的子数组(C++)


本文参考卡哥的代码随想录,加上了一点自己的理解,感谢Carl的分享。
代码随想录
视频讲解

一. 题目

力扣209题
给定一个含有 n 个正整数的数组和一个正整数 target ,找出该数组中满足其和 ≥ target 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。

示例:

输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
提示:

1 <= target <= 10^9
1 <= nums.length <= 10^5
1 <= nums[i] <= 10^5

二. 解题思路

1.暴力解法

使用两个指针,第一个指针nums[i]用来遍历数组。nums[i]指向数组的第一个元素后,第二个指针nums[j]依次指向后面的元素,目的是将第一个元素与后面的元素依次相加,直到判断出__nums[i]与nums[j]所指区间的所有元素和__>=target时,记录元素个数result,此时的result一定为以 nums[i]指向的元素 为起点满足元素和>=target的最少个数。然后,将nums[i]后移一位,判断以第二个元素为起点满足条件的数组中元素个数,若是小于先前的result则进行替换。以此类推,直到指针nums[i]遍历完整个数组。

代码思路:
(result:目标数组长度;i:第一个数组指针下标;j:第二个数组指针下标;subLength:i,j 之间数组长度;sum:i,j 之间数组元素之和 )

  • 初始化result为最大整数(INT32_MAX),以便后期可以作比较。
  • 写第一个for循环用来遍历数组,且每次指针后移的时候,sum 清零。
  • for循环嵌套里第二个for循环(j = i,为起点),取所有和。
    • 如果达到期待值,计算数组长度,result值更新为最小长度。跳出第二个循环。
  • 最后返回result,若没有符合条件的数组,则返回0。

2.滑动窗口

同样使用两个指针,初始化两个指针nums[i], nums[j]均指向数组的首元素。不同之处在于我们用靠右的指针nums[j]来遍历数组。随着nums[j]的右移,依次相加各元素,记和为sum。当sum达到条件时进入循环,元素和sum减去nums[i]的值,数组指针nums[i]右移。继续判断sum是否满足条件,不断右移i,减掉nums[i]的值,直到sum<target,此时i,j范围内的数组为以nums[j]为尾端满足条件的最短数组。一次循环结束。将 j 右移,继续下次的循环判断。

代码思路:

  • 初始化result为最大整数(INT32_MAX),以便后期可以作比较。
  • 写第一个for循环( j )用来遍历数组。j右移的同时依次相加元素,记和为sum。
  • 当sum>=target时,进入循环。计算数组长度subLength = j - i + 1比较并更新result。sum = sum-num[i] ,右移i。直到sum不满足条件,跳出while循环,进入for循环,判断下一个以num[j]为末端的满足条件的数组。
  • 最后返回result,若没有符合条件的数组,则返回0。

三. 代码

暴力解法

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int result = INT32_MAX; //将result设为最大值,以便后面比较
        int sum = 0;
        int subLength = 0;
        for ( int i = 0; i < nums.size(); i++) {
            sum = 0;
            for ( int j = i; j < nums.size(); j++) {
                sum += nums[j]; //计算数组和
                if ( sum >= target) {
                    subLength = j - i + 1; //计算数组长度
                    result = result < subLength ? result : subLength; //选择最小长度
                    break;
                }
            }
        }
        return result == INT32_MAX ? 0 : result; //没有期待数组则返回0
    }
};

滑动窗口解法

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int result = INT32_MAX; //将result设为最大值,以便后面比较
        int sum = 0;
        int i = 0;
        int subLength = 0;
        for( int j = 0; j <nums.size(); j++) {
            sum += nums[j];
            while (sum >= target) {
                subLength = j - i + 1; 
                result = result < subLength ? result : subLength;
                sum -= nums[i];
                i++;
            }
        }
        return result == INT32_MAX ? 0 : result;
    }
};

在这里插入图片描述

要“最小子数组最大累加和”,我们通常指的是如下这类问题: > 给定一个正整数数组 `nums` 和一个正整数 `m`,将这个数组分割成 `m` 个**非空连续子数组**。你的任务是让这 `m` 个子数组中的**最大累加和尽可能小**。 --- ## ✅ 问题理解 ### 示例输入: ```cpp nums = [7,2,5,10,8] m = 2 ``` ### 输出: ``` 18 ``` 解释:分成 `[7,2,5]` 和 `[10,8]`,两个子数组的和分别是 `14` 和 `18`,最大为 `18`,这是最优解。 --- ## ✅ 解题思路:二分 + 贪心验证 我们可以使用 **二分法 + 判断函数** 的方式来解决这个问题: ### 🧠 核心思想: - 设一个值 `mid`,表示我们允许的最大子数组和。 - 检查是否能将数组划分为若干个子数组,使得每个子数组的和不超过 `mid`,并且子数组数量 ≤ m。 - 如果可以,则尝试更小的 `mid`;否则需要增大 `mid`。 --- ## ✅ 二分法框架 - 左边界:`max(nums)`(每个子数组至少包含一个元素) - 右边界:`sum(nums)`(整个数组作为一个子数组) 每次判断是否可以在当前 `mid` 下划分出 ≤ m 个子数组。 --- ## ✅ C++ 实现代码 ```cpp #include <iostream> #include <vector> using namespace std; class Solution { public: // 判断是否能用最大和为 maxSum 的方式把数组分成 <= m 个子数组 bool canSplit(vector<int>& nums, int m, int maxSum) { int count = 1; long long currentSum = 0; for (int num : nums) { if (currentSum + num > maxSum) { count++; currentSum = num; if (count > m) return false; } else { currentSum += num; } } return true; } int splitArray(vector<int>& nums, int m) { long long left = 0, right = 0; // 找到左右边界 for (int num : nums) { left = max(left, (long long)num); right += num; } // 二分查找 while (left < right) { long long mid = left + (right - left) / 2; if (canSplit(nums, m, mid)) { right = mid; } else { left = mid + 1; } } return left; } }; int main() { vector<int> nums = {7,2,5,10,8}; int m = 2; Solution sol; cout << "最小可能的最大子数组和: " << sol.splitArray(nums, m) << endl; // 输出 18 return 0; } ``` --- ## 🔍 函数解释 | 函数 | 功能 | |------|------| | `canSplit()` | 贪心地划分数组,检查是否能在 `mid` 的限制下分成 ≤ m 个子数组 | | `splitArray()` | 主函数,执行二分搜索,找到最小的最大子数组和 | --- ## 🚀 时间复杂度分析 - 二分范围是 `[max(nums), sum(nums)]`,最多约 $10^9$ - 每次 `canSplit` 是 O(n) - 总体时间复杂度:**O(n * log S)**,其中 S 是数组总和 --- ## ✅ 应用场景 这种技巧适用于以下类型的问题: | 题目编号 | 题意 | |---------|------| | LeetCode 410 | 分割子数组使最大和最小化 | | 类似题目 | 最小化最大段长度、最少天数完成阅读计划等 | --- ##
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值