1. 累加和等于num的最长子数组的长度(有负数)
注:数组元素可为负数
思路:求子数组问题,就是依次考虑数组中每一位元素之前的数组的情况。
比如说arr = {7,3,2,1,1,7,7}
,依次考虑数组范围(0-i)中,任意元素开始,以arr[i]
结尾的子数组是否符合条件。比如说i=0时,子数组{7}
符合条件;i=4时,子数组{3,2,1,1}
符合条件。
假设遍历到i位置,数组元素从0加到i的值为sum,只需要求i之前的数组中,是否出现过和为sum-num的情况。例如,arr = {9,8,3,2,1,1,7,7},num=7
,当遍历到i=5的时候,sum=24,此时sum-num=24-7=17。因为当i=1时,sum=17,所以下标从2到5,数组元素之和肯定为sum。
public static int maxLength(int[] arr, int k) {
if (arr == null || arr.length == 0) {
return 0;
}
HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
map.put(0, -1); // important
int len = 0;
int sum = 0;
for (int i = 0; i < arr.length; i++) {
sum += arr[i];
if (map.containsKey(sum - k)) {
len = Math.max(i - map.get(sum - k), len);
}
if (!map.containsKey(sum)) {
map.put(sum, i);
}
}
return len;
}
2. 累加和等于num的最长子数组的长度(无负数)
题目;给定一个数组arr, 全是正数; 一个整数aim, 求累加和等于aim的, 最长子数组, 要求额外空间复杂度O(1), 时间复杂度O(N)
思路:可以使用双指针,一个在左,一个在右,形成一个窗口区域。当窗口中的值小于aim时,窗口右边界扩大。当窗口中的值大于或等于aim时,由于数组中全为正数,所以窗口右边界再扩大,窗口中的值会大于aim。所以此时需要窗口左边界右移。当窗口右边界到达数组最后一个元素,且窗口中的值小于aim,返回结果。
数组中有负数和无负数的区别:
窗口扩大,窗口内的累加和一定增加,窗口缩小,窗口内的累加和一定减小。
如有数组arr={7,8,-8},aim=7
,当窗口右边界移动到0时,窗口内的值已经是7了,这时候窗口会右移。但是,其实最长的子数组为{7,8,-8}
。所以有负数时,不能用此方法。
public static int getMaxLength(int[] arr, int k) {
if (arr == null || arr.length == 0 || k <= 0) {
return 0;
}
int L = 0;
int R = 0;
int sum = arr[0];
int len = 0;
while (R < arr.length) {
if (sum == k) {
len = Math.max(len, R - L + 1);
sum -= arr[L++];//窗口左边界右移
} else if (sum < k) {
R++;//当前窗口内的值小于aim,窗口右边界右移,扩大窗口
if (R == arr.length) {
break;
}
sum += arr[R];
} else {//当前窗口内的值大于aim,窗口左边界右移
sum -= arr[L++];
}
}
return len;
}
3. 累加和小于等于num的最长子数组的长度
题目:给定一个数组arr, 值可正, 可负, 可0; 一个整数aim, 求累加和小于等于aim的, 最长子数组, 要求时间复杂度O(N)
思考:这一题要求小于等于,且数组元素有负数。就不能使用双指针来构造滑动窗口了。因为给定数组中有负数,在窗口扩大,窗口内的值不一定增大;窗口缩小,窗口内的值不一定减小。
思路:设计两个数组max_sum
和max_sum_index
,长度为给定数组的长度。数组元素max_sum[i]
表示以arr[i]
开头,累加和最小的最长子数组的累加和;数组元素max_sum_index[i]
与数组元素max_sum[i]
相对应,表示以arr[i]
开头,累加和最小的最长子数组的右边界下标。
通过反向遍历数组,得到数组max_sum
和max_sum_index
。
例如,有数组arr={6,5,1,7,2,-6,-3,4,3,-2,1},aim=6
,则数组max_sum
和max_sum_index
的情况如下所示
然后根据数组max_sum_index
可将元素组划分成几个子数组。
可以将原数组看成如下数组,该数组中肯定不会出现负数,所以可以对其进行滑动窗口操作。
滑动窗口,遍历过程中,满足条件的子数组如下(自动舍弃长度更小的子数组组合)
然后分别计算对应的长度即可。第三个组合对应的数组为{1,7,2,-6,-3,4,3,-2}
,长度为8。
class Solution_MaxLength3{
public static int maxLengthAwesome(int[] arr, int aim) {
if (arr == null || arr.length == 0) {
return 0;
}
int[] max_sum = new int[arr.length];
int[] max_sum_index = new int[arr.length];
max_sum[arr.length - 1] = arr[arr.length - 1];
max_sum_index[arr.length - 1] = arr.length - 1;
for (int i = arr.length - 2; i >= 0; i--) {
if (max_sum[i + 1] < 0) {
max_sum[i] = arr[i] + max_sum[i + 1];
max_sum_index[i] = max_sum_index[i + 1];
} else {
max_sum[i] = arr[i];
max_sum_index[i] = i;
}
}
int sum = 0;
int len = 0;
int start = 0;//窗口左边界
int end = 0;//窗口右边界
int next = 0;//要加进窗口的子数组中的第一个元素下标
while(start < arr.length){
while(next < arr.length && sum + max_sum[next] <= aim){
sum += max_sum[next];
end = max_sum_index[next];
next = end + 1;
}
len = Math.max(len, next - start);
//窗口左边界右移
if(end > start){
sum -= max_sum[start];
}else{//如果end == start,则有以后sum值为0
sum = 0;
}
start = max_sum_index[start] + 1;
}
return len;
}
public static void main(String[] args) {
int[] arr = {6,5,1,7,2,-6,-3,4,3,-2,3};
System.out.println(maxLengthAwesome(arr, 6));
}
}