题目
力扣https://leetcode-cn.com/problems/minimum-size-subarray-sum/
题解
方法一:自己写的解
求和的地方不够优,见方法三大牛的写法
package algorithm.arrayListTest;
public class Solution_8 {
/**
* 输入:target = 7, nums = [2,3,1,2,4,3]
* 输出:2
* 解释:子数组 [4,3] 是该条件下的长度最小的子数组
*/
public static void main(String[] args) {
System.out.println(new Solution_8().minSubArrayLen(7, new int[]{2, 3, 1, 2, 4, 3}));
}
public int minSubArrayLen(int target, int[] nums) {
int left = 0, right = 0, n = nums.length;
int ans = Integer.MAX_VALUE;
while (left <= right && right < n) {
int sumOfWindow = sumSubArrays(nums, left, right);
if (sumOfWindow >= target) {
ans = Math.min(ans, right - left + 1);
left++;
} else {
right++;
}
}
if (ans == Integer.MAX_VALUE) {
return 0;
}
return ans;
}
private int sumSubArrays(int[] nums, int left, int right) {
if (left > right) {
return 0;
}
if (left == right) {
return nums[left];
}
int sum = 0;
for (int i = left; i <= right && i < nums.length; i++) {
sum += nums[i];
}
return sum;
}
}
方法二:前缀和 + 二分查找
- 因为这道题保证了数组中每个元素都为正,所以前缀和一定是递增的,这一点保证了二分的正确性
- 需要理解二分查找中,返回负数表示什么,我刚开始也没想明白
- 先把数据处理一遍,将前i个元素累加和存到另外一个数组,这个好像和动态规划有点像,求满足条件的最小子序列长度,还没实现这个版本,等练习了动规再来看
public int minSubArrayLen(int target, int[] nums) {
int n = nums.length;
if (n == 0) {
return 0;
}
int ans = Integer.MAX_VALUE;
/**
* 为了方便计算,令 size = n + 1
* 其中 sums[i] 表示从 nums[0]到 nums[i-1]的元素和
* sums[0] = 0 表示前 0 个元素的前缀和为 0
* sums[1] = nums[0] 表示前 1 个元素的前缀和为 nums[0]
* 以此类推
*/
int[] sums = new int[n + 1];
for (int i = 1; i <= n; i++) {
//nums[i - 1] 表示第i个元素,因为从下标0开始计算
sums[i] = sums[i - 1] + nums[i - 1];
}
for (int i = 1; i <= n; i++) {
int searchValue = target + sums[i - 1];
//此处的 sums[bound] 表示前 bound个值的和,bound为下标
//sums[i - 1] + target <= sums[bound]
int bound = binarySearch(sums, 0, sums.length, searchValue);
//表示没找到
if (bound < 0) {
bound = -bound - 1;
//这里的bound也是答案的一种
}
if (bound <= n) {
ans = Math.min(ans, bound - (i - 1));
}
}
return ans == Integer.MAX_VALUE ? 0 : ans;
}
/**
* 到了就返回目标值得下标位置
* 找不到就返回-(low + 1),负数是为了返回没找到,但是第一个大于目标值值得位置信息,需要将返回值取反-1就得到第一个大于目标值得位置
*/
private Integer binarySearch(int[] a, int fromIndex, int toIndex,
int key) {
int low = fromIndex;
int high = toIndex - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
int midVal = a[mid];
if (midVal < key)
low = mid + 1;
else if (midVal > key)
high = mid - 1;
else
return mid; // key found
}
//-(第一个大于目标值得下标位置 + 1)
return -(low + 1); // key not found.
}
方法三:滑动窗口
- 滑动窗口内的值可以动态的计算,我自己写的方法一中的是每次都计算,复杂了
- 滑动窗口的常规需要记住的一些套路
- 窗口缩小:左指针移动一个
- 窗口增加:右指针移动一个
- 窗口整体移动,且窗口大小不变:左右指针都移动一个位置
- 求滑动窗口的累加和
- 求滑动窗口的窗口两端的差值
- 滑动窗口测速
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int n = nums.length;
if (n == 0) {
return 0;
}
int ans = Integer.MAX_VALUE;
int start = 0, end = 0;
int sum = 0;
while (end < n) {
//如果窗口向右边增加一个元素,窗口之内的元素累加和也要增加一个
sum += nums[end];
while (sum >= target) {
ans = Math.min(ans, end - start + 1);
//如果窗口左边缩小一个,窗口之内的元素累加和也要减去缩减的那个元素
sum -= nums[start];
start++;
}
end++;
}
return ans == Integer.MAX_VALUE ? 0 : ans;
}
}