题目地址:
https://www.lintcode.com/problem/minimum-size-subarray-sum/description
给定一个正整数数组,再给定一个数 s s s,问数组中和大于等于 s s s的最短子区间的长度的最小值。如果不存在返回 − 1 -1 −1。
具体思路是,先求前缀和数组,再用双指针。只需要注意快指针再向前走的时候,慢指针永远不会后退。代码如下:
public class Solution {
/**
* @param nums: an array of integers
* @param s: An integer
* @return: an integer representing the minimum size of subarray
*/
public int minimumSize(int[] nums, int s) {
// write your code here
int[] sum = new int[nums.length + 1];
for (int i = 0; i < nums.length; i++) {
sum[i + 1] = sum[i] + nums[i];
}
// 先初始化minLen为一个不可能取到的大数
int minLen = nums.length + 1;
// 初始化快慢指针为0,i是慢指针,j是快指针
for (int j = 0, i = 0; j < nums.length; j++) {
// 如果慢指针在快指针后面,我们就开始对区间[i,j]做判断
while (i <= j) {
// 如果该区间的和大于等于s,就更新minLen,并将慢指针后移,
// 看看能不能得到更小的满足条件的区间
if (sum[j + 1] - sum[i] >= s) {
minLen = Math.min(minLen, j - i + 1);
i++;
} else {
// 否则说明当前区间的和太小,需要拓宽右边界
break;
}
}
}
return minLen == nums.length + 1 ? -1 : minLen;
}
}
时空复杂度 O ( n ) O(n) O(n)。
算法正确性证明:
这个算法本质是枚举算法,但做了一些优化。很显然右端点的所有情况都枚举过了,而左端点是从
0
0
0开始枚举的,每次移动的时候都只是在区间和满足条件的情况下尝试寻找更优解,这个更优解的区间左端点一定不会位于当前慢指针的左边(否则就得到了更长的满足条件的子数组,矛盾了)。所以相当于枚举的时候做了剪枝,正确性仍然是保持的。
当然也可以不用前缀和数组,而用一个变量sum来记录当前区间的数字和。这样可以节省空间。代码如下:
public class Solution {
/**
* @param nums: an array of integers
* @param s: An integer
* @return: an integer representing the minimum size of subarray
*/
public int minimumSize(int[] nums, int s) {
// write your code here
int sum = 0;
int minLen = nums.length + 1;
for (int j = 0, i = 0; j < nums.length; j++) {
sum += nums[j];
while (i <= j) {
if (sum >= s) {
// 如果当前sum大于等于s,那就更新minLen,
// 并且先删除掉左端点的数,并尝试缩小左边界
minLen = Math.min(minLen, j - i + 1);
sum -= nums[i];
i++;
} else {
// 否则拓宽右边界
break;
}
}
}
return minLen == nums.length + 1 ? -1 : minLen;
}
}
时间复杂度不变,空间 O ( 1 ) O(1) O(1)。