LeetCode209之长度最小的子数组(相关话题:滑动窗口,前缀和,二分)

题目描述

给定一个含有 n 个正整数的数组和一个正整数 target 。找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 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 <= target <= 109
1 <= nums.length <= 105
1 <= nums[i] <= 105

方法一:滑动窗口

代码基本直接套板子

public int minSubArrayLen(int target, int[] nums) {
    int minLen = Integer.MAX_VALUE;
    int left = 0, sum = 0;

    for (int right = 0; right < nums.length; right++) {
        sum += nums[right];

        while (sum >= target) {
            minLen = Math.min(minLen, right - left + 1);
            sum = sum -  nums[left];
            left++;
        }
    }

    return minLen == Integer.MAX_VALUE ? 0 : minLen;
}

方法二:前缀和加二分 

为了使用二分查找,需要额外创建一个数组 sums 用于存储数组 nums 的前缀和,其中 sums[i] 表示从nums[0] 到 nums[i−1] 的元素和。得到前缀和之后,对于每个开始下标 i,可通过二分查找得到大于或等于 i的最小下标 bound,使得 sums[bound]-sums[i] ≥s,并更新子数组的最小长度(此时子数组的长度是bound-i

因为这道题保证了数组中每个元素都为正,所以前缀和一定是递增的,这一点保证了二分的正确性。如果题目没有说明数组中每个元素都为正,这里就不能使用二分来查找这个位置了。

public int minSubArrayLen(int s, int[] nums) {

        int n = nums.length;
        if (n == 0) {
            return 0;
        }
        int ans = Integer.MAX_VALUE;
        int[] sums = new int[n + 1]; 
        // 为了方便计算,令 size = n + 1 
        // sums[0] = 0 意味着前 0 个元素的前缀和为 0
        // sums[1] = A[0] 前 1 个元素的前缀和为 A[0]
        // 以此类推
        for (int i = 1; i <= n; i++) {
            sums[i] = sums[i - 1] + nums[i - 1];
        }
        for (int i = 0; i <= n; i++) {
            int target = s + sums[i];
            //二分如果无法找到目标值返回的下标是第二个比目标值大的下标的相反数(见二分测试)
            int bound = Arrays.binarySearch(sums, target);
            if (bound < 0) {
                //第一个前缀和大于target的下标一定是最符合条件的下标
                bound = -bound - 1;
            }
            if (bound <= n) {
                ans = Math.min(ans, bound - (i));
            }
        }
        return ans == Integer.MAX_VALUE ? 0 : ans;
   
 }
二分测试

题型变种

一串首尾相连的珠子(m个),有N种颜色(N<=10),设计一个算法,取出其中一段,要求包含所有N中颜色,并使长度最短。并分析时间复杂度与空间复杂度

算法步骤

  1. 初始化

    • 使用一个哈希表(或数组)来跟踪窗口中每种颜色的珠子数量。
    • 设置两个指针,startend,表示当前考虑的珠子段的起始和结束位置。
  2. 扩展窗口

         移动 end 指针以扩展窗口,直到窗口包含所有 N 种颜色的珠子。
  3. 收缩窗口

    • 一旦窗口包含所有颜色,尝试移动 start 指针以收缩窗口,直到窗口不再包含所有颜色。
    • 每次收缩窗口后,检查当前窗口长度是否是到目前为止找到的最短长度,如果是,则更新结果。
  4. 处理首尾相连

          由于珠子是首尾相连的,我们需要处理环形数组。这可以通过模拟扩展原数组的方式来          实现,即将原数组复制一遍附在其后。

Java代码 

import java.util.HashMap;
import java.util.Map;

public class Solution {
    public int[] shortestBeads(int[] beads, int N) {
        Map<Integer, Integer> count = new HashMap<>(); // 存储当前窗口中每种颜色珠子的数量
        int start = 0; // 窗口的起始位置
        int min_length = Integer.MAX_VALUE; // 存储找到的最短段的长度
        int[] result = new int[2]; // 存储最短段的起始和结束位置

        // 模拟扩展数组,因为珠子是首尾相连的
        int[] extended_beads = new int[beads.length * 2];
        System.arraycopy(beads, 0, extended_beads, 0, beads.length);
        System.arraycopy(beads, 0, extended_beads, beads.length, beads.length);

        // 遍历扩展后的珠子数组
        for (int end = 0; end < extended_beads.length; end++) {
            // 将当前珠子加入窗口,并更新该颜色珠子的数量
            count.put(extended_beads[end], count.getOrDefault(extended_beads[end], 0) + 1);

            // 当窗口包含所有 N 种颜色时,尝试收缩窗口
            while (count.size() == N) {
                // 如果当前窗口长度小于已知的最短长度,则更新最短长度和结果
                if (end - start + 1 < min_length) {
                    min_length = end - start + 1;
                    result[0] = start;
                    result[1] = end;
                }

                // 从窗口中移除起始位置的珠子,并更新计数
                count.put(extended_beads[start], count.get(extended_beads[start]) - 1);
                // 如果某种颜色的珠子数量减少到0,则从计数中移除该颜色
                if (count.get(extended_beads[start]) == 0) {
                    count.remove(extended_beads[start]);
                }
                // 收缩窗口的起始位置
                start++;
            }
        }

        // 返回最短段的起始和结束位置
        return result;
    }
   
}

Python代码

def shortestBeads(beads, N):
    count = {}  # 用于存储当前窗口中每种颜色珠子的数量
    start = 0  # 窗口的起始位置
    min_length = float('inf')  # 存储找到的最短段的长度
    result = (0, 0)  # 存储最短段的起始和结束位置

    # 模拟扩展数组,因为珠子是首尾相连的
    extended_beads = beads * 2

    # 遍历扩展后的珠子数组
    for end in range(len(extended_beads)):
        # 将当前珠子加入窗口,并更新该颜色珠子的数量
        count[extended_beads[end]] = count.get(extended_beads[end], 0) + 1

        # 当窗口包含所有 N 种颜色时,尝试收缩窗口
        while len(count) == N:
            # 如果当前窗口长度小于已知的最短长度,则更新最短长度和结果
            if end - start + 1 < min_length:
                min_length = end - start + 1
                result = (start, end)

            # 从窗口中移除起始位置的珠子,并更新计数
            count[extended_beads[start]] -= 1
            # 如果某种颜色的珠子数量减少到0,则从计数中移除该颜色
            if count[extended_beads[start]] == 0:
                del count[extended_beads[start]]
            # 收缩窗口的起始位置
            start += 1

    # 返回最短段的起始和结束位置
    return result

# 示例
beads = [1, 2, 3, 4, 1, 1, 1, 2, 3]
N = 4
print(shortestBeads(beads, N))  # 输出最短段的起始和结束位置

博主总结

这道题可以有效的复习之前学习过的各种解题技巧,非常值得练习

LeetCode239之滑动窗口最大值(相关话题:单调队列)-CSDN博客

LeetCode076最小覆盖子串(相关话题:双指针,滑动窗口)_leetcode最小覆盖子串-CSDN博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

数据与后端架构提升之路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值