问题描述:
给定一个数组 arr,该数组无序,但每个值均为正数,再给定一个正数 k。求 arr 的所有子 数组中所有元素相加和为 k 的最长子数组长度。要求:时间复杂度O(N),额外空间复杂度O(1)
输入: arr=[1,2,1,1,1]
输出:k=3
累加和为 3 的最长子数组为[1,1,1],所以结果返回 3。
解题思路:
利用滑动窗口解决问题
- 起始窗口内只有第一个数,判断第一个数和k的大小。
- 如果窗口内的数字的和大于k,则需要将窗口左边界向右移,继续比较
- 如果窗口内的数字的和小于k,则需要将窗口右边界向右移,继续比较
public static int getMaxLength(int[] arr, int k) {
if (arr == null || arr.length == 0 || k <= 0) {
return 0;
}
int left = 0;
int right = 0;
int sum = arr[0];
int len = 0;
while (right < arr.length) {
if (sum == k) {
len = Math.max(len, right - left + 1);
sum -= arr[left++];
} else if (sum < k) {
right++;
if (right == arr.length) {
break;
}
sum += arr[right];
} else {
sum -= arr[left++];
}
}
return len;
}
问题进阶:
给定一个无序数组 arr,其中元素可正、可负、可 0,给定一个整数 k。求 arr 所
有的子数组 中累加和小于或等于 k 的最长子数组长度。
输入:arr=[3,-2,-4,0,6],k=-2
相加和小于或等于-2 的最长子数组为{3,-2,-4,0},所以结果返回4。
解题思路:
需要两个辅助数组,一个是minSums,表示当前位置向后看,和小于k的最长长度的数的和
一个是minEnds,表示最长长度的结束的索引。两个数组从右向左生成。
以例题为例,构建的两个数组为
Coding
public static int maxLengthAwesome(int[] arr, int k) {
if (arr == null || arr.length == 0) {
return 0;
}
int[] minSums = new int[arr.length];
int[] minSumEnds = new int[arr.length];
minSums[arr.length - 1] = arr[arr.length - 1];
minSumEnds[arr.length - 1] = arr.length - 1;
for (int i = arr.length - 2; i >= 0; i--) {
if (minSums[i + 1] < 0) {
minSums[i] = arr[i] + minSums[i + 1];
minSumEnds[i] = minSumEnds[i + 1];
} else {
minSums[i] = arr[i];
minSumEnds[i] = i;
}
}
int end = 0;
int sum = 0;
int res = 0;
// i是窗口的最左的位置,end是窗口最右位置的下一个位置
for (int i = 0; i < arr.length; i++) {
// while循环结束之后:
// 1) 如果以i开头的情况下,累加和<=k的最长子数组是arr[i..end-1],看看这个子数组长度能不能更新res;
// 2) 如果以i开头的情况下,累加和<=k的最长子数组比arr[i..end-1]短,更新还是不更新res都不会影响最终结果;
while (end < arr.length && sum + minSums[end] <= k) {
sum += minSums[end];
end = minSumEnds[end] + 1;
}
res = Math.max(res, end - i);
if (end > i) { // 窗口内还有数
sum -= arr[i];
} else { // 窗口内已经没有数了,说明从i开头的所有子数组累加和都不可能<=k
end = i + 1;
}
}
return res;
}