算法题 长度最小的子数组

209. 长度最小的子数组

问题描述

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

示例

示例 1:

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

示例 2:

输入:target = 4, nums = [1,4,4]
输出:1

示例 3:

输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0

算法思路

滑动窗口(双指针)

  1. 初始化
    • 左指针 left = 0
    • 当前窗口和 windowSum = 0
    • 最小长度 minLen = Integer.MAX_VALUE
  2. 移动右指针
    • 遍历数组(右指针 right 从 0 开始)
    • nums[right] 加入窗口和 windowSum
  3. 收缩左边界
    • windowSum ≥ target 时:
      • 更新最小长度 minLen = min(minLen, right-left+1)
      • 从窗口和中减去 nums[left]
      • 左指针右移 left++
  4. 返回结果
    • minLen 仍是初始值,返回 0
    • 否则返回 minLen

代码实现

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int left = 0;             // 窗口左指针
        int windowSum = 0;        // 当前窗口和
        int minLen = Integer.MAX_VALUE; // 最小长度(初始设为极大值)
        
        for (int right = 0; right < nums.length; right++) {
            // 将右指针元素加入窗口
            windowSum += nums[right];
            
            // 当窗口和满足条件时收缩左边界
            while (windowSum >= target) {
                // 更新最小长度
                minLen = Math.min(minLen, right - left + 1);
                // 移除左边界元素并右移左指针
                windowSum -= nums[left];
                left++;
            }
        }
        
        // 若未找到有效子数组返回0,否则返回最小长度
        return minLen == Integer.MAX_VALUE ? 0 : minLen;
    }
}

算法分析

  • 时间复杂度:O(n)
    每个元素最多被访问两次(右指针加入窗口一次,左指针移除一次)
  • 空间复杂度:O(1)
    仅使用常数空间

算法过程

target=7, nums=[2,3,1,2,4,3]

步骤  窗口     窗口和  操作
0    [2]       2    不满足
1    [2,3]     5    不满足
2    [2,3,1]   6    不满足
3    [2,3,1,2] 8   满足 → 记录长度4,左指针右移
     [3,1,2]   6    不满足
4    [3,1,2,4] 10  满足 → 记录长度4,左指针右移
     [1,2,4]   7   满足 → 记录长度3,左指针右移
     [2,4]     6    不满足
5    [2,4,3]   9   满足 → 记录长度3,左指针右移
     [4,3]     7   满足 → 记录长度2(最小),左指针右移
     [3]       3    不满足
结果:minLen=2

测试用例

public static void main(String[] args) {
    Solution solution = new Solution();
    
    // 测试用例1:标准示例
    int[] nums1 = {2,3,1,2,4,3};
    System.out.println("Test 1: " + solution.minSubArrayLen(7, nums1)); // 2
    
    // 测试用例2:单个元素满足
    int[] nums2 = {1,4,4};
    System.out.println("Test 2: " + solution.minSubArrayLen(4, nums2)); // 1
    
    // 测试用例3:需整个数组
    int[] nums3 = {1,2,3,4,5};
    System.out.println("Test 3: " + solution.minSubArrayLen(15, nums3)); // 5
    
    // 测试用例4:无解
    int[] nums4 = {1,1,1,1};
    System.out.println("Test 4: " + solution.minSubArrayLen(10, nums4)); // 0
    
    // 测试用例5:多个解取最小
    int[] nums5 = {1,2,3,4,1,2,3};
    System.out.println("Test 5: " + solution.minSubArrayLen(7, nums5)); // 2([3,4]或[4,1,2]取最小)
    
    // 测试用例6:边界值
    int[] nums6 = {};
    System.out.println("Test 6: " + solution.minSubArrayLen(1, nums6)); // 0
}

关键点

  1. 滑动窗口条件

    • 右指针移动:扩大窗口直至满足条件
    • 左指针移动:在满足条件时收缩窗口寻找最小长度
  2. 正整数的关键作用

    • 保证窗口扩展时和单调递增
    • 收缩窗口时和单调递减
  3. 边界处理

    • 空数组直接返回 0
    • 未找到解时返回 0(通过 minLen 初始值判断)

常见问题

  1. 为什么使用滑动窗口?
    数组元素均为正整数,具有单调性,适合用滑动窗口在 O(n) 时间复杂度内解决。

  2. 如何处理多个解的情况?
    在满足条件时持续收缩左边界,记录所有可能的最小长度。

  3. 如果数组包含负数/零?
    滑动窗口失效,需使用前缀和+二分查找(时间复杂度 O(n log n))。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值