题目描述
给定一个含有 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中颜色,并使长度最短。并分析时间复杂度与空间复杂度
算法步骤
-
初始化:
- 使用一个哈希表(或数组)来跟踪窗口中每种颜色的珠子数量。
- 设置两个指针,
start
和end
,表示当前考虑的珠子段的起始和结束位置。
-
扩展窗口:
移动end
指针以扩展窗口,直到窗口包含所有 N 种颜色的珠子。 -
收缩窗口:
- 一旦窗口包含所有颜色,尝试移动
start
指针以收缩窗口,直到窗口不再包含所有颜色。 - 每次收缩窗口后,检查当前窗口长度是否是到目前为止找到的最短长度,如果是,则更新结果。
- 一旦窗口包含所有颜色,尝试移动
-
处理首尾相连:
由于珠子是首尾相连的,我们需要处理环形数组。这可以通过模拟扩展原数组的方式来 实现,即将原数组复制一遍附在其后。
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)) # 输出最短段的起始和结束位置
博主总结
这道题可以有效的复习之前学习过的各种解题技巧,非常值得练习