问题描述1:给定一个数组,值全部是正数,请返回累加值为给定值 k 的最长子数组的长度。
解题思路:
一开始 l 和 r 均指向数组第一个数,sum=arr[ 0 ]。
如果k>sum,(不知道为什么写sum小于(<)k就是没法显示后面的,只能这么写了(┬_┬)很醉)那么 r 向右移动,sum加上 arr[ r ]。如果sum>k,那么sum去掉此时 l 的值,并且 l 右移。重复上述过程直至 r 到达数组尾部。
如果sum==k ,那么记录下此时的长度,如果比maxlength大,那么更新maxlength,并且sum减去arr [ l ],l 向右移动。
代码如下:
public static int len1(int[] arr, int k) {
int len=0;
int l=0,r=0;
int sum=arr[0];
while(r<arr.length){
if (sum==k) {
len=Math.max(len, r-l+1);
sum-=arr[l++];//如果sum等于k,sum的值减去l位置的值,l右移
}else if (sum<k) {
r++;
if (r==arr.length-1) {
break;
}
sum+=arr[r];
}else {
sum-=arr[l++];
}
}
return len;
}
问题描述2:给定一个数组,值可以为正数负数和0,请返回累加和为给定值 K 的最长子数组长度。
解题思路:
假设在长度为n的数组arr中,整个数组的和为sum,要求出以n-1结尾的累加和为K的最长子数组。以上图为例,如果最长子数组是从 m+1 到 n-1 的,那么前面 0 到 m 的和一定是 sum-k ,而且此时 m 位置是数组中第一次出现累加和为 sum-k 的位置。
可以简单证明一下:如果在m前面还有累加和为sum-k的情况的位置 p,那么以n-1结尾累加和为k的最长子数组的长度就不是m-1到n-1,而应该是p+1到n-1,此时子数组长度变长,与之前给的条件不相符。所以此时m位置一定是数组中第一次累加和出现sum-k的位置。
我们可以用一个map来存储数组中第一次出现累加和sum和它对应的index的值。
在每一次查找时,先在map中找是否有存在sum-k的key,(i为出现sum的下标,j为sum-k对应的下标)如果有,那么此时的子数组的长度就是 i-j,如果不存在,就将(sum,i)插入map中。
但是按照上面的方法,可能会将0位置跳过,以下面的数组举例:
value | 4 | 6 | 7 | 9 | 8 |
---|---|---|---|---|---|
index | 0 | 1 | 2 | 3 | 4 |
如果k=4,在index=0时,sum=4,此时在map中查找是否存在key=sum-k=0的情况,但是map中为空,查不到,所以就直接跳过了,没有进行任何处理,maxlength还是0,最后的返回值就是0,可是预期的结果应该是1。所以为了避免这种情况的发生,应该在一开始就讲(0,-1)插入map中。
代码如下:
public static int len2(int[] arr,int k) {
if(arr==null||k<0||arr.length==0)
return 0;
int len=0;
HashMap<Integer, Integer> map=new HashMap<Integer,Integer>();
map.put(0, -1);
int sum=0;
for(int i=0;i<arr.length;i++){
sum+=arr[i];//求出到i位置的和
if (map.containsKey(sum-k)) {
//如果map中记录了sum-k这个值对应的index
//更新len的长度
len=Math.max(i-map.get(sum-k), len);
}
if (!map.containsKey(sum)) {
//如果map中没有记录sum
//那么就将sum插入map中
map.put(sum, i);
}
}
return len;
}
问题描述3:给定一个数组,值可以为正数负数和0,请返回累加和不大于k的最长子数组长度。
解题思路:
分两步来计算:先计算以每个数开头的最小累加和以及他们的右边界,再将累加和累加起来直至大于k。
首先计算以 i 开头往后的累加和,从数组尾部开始,举例如下:
arr_index | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|---|
arr_value | 4 | 3 | -2 | 6 | 7 | -3 | -1 |
min_value | 4 | 1 | -2 | 6 | 3 | -4 | -1 |
min_index | 0 | 2 | 2 | 3 | 6 | 6 | 6 |
其中min_value是最小累加和,min_index是对应的右边界。从右往左遍历数组,如果要求出 i 位置的最小累加和,那么就要看 i+1 位置的最小累加和,如果min_value[ i+1 ]<0,那么min_value[ i ]=arr[ i ]+min_value[ i+1 ],min_index[ i ]=min_index[ i+1 ],否则min_value[ i ]=arr[ i ],min_index[ i ]=i。
接下来再从左往右遍历数组,如果以 i 开头的最小和>k,那么再往后面累加的累加和都>k。
上图中的sum1,sum2,sum3,sum4等块中包含至少一个数,从l=0位置开始遍历,如果s+sum1<=k那么s+=sum1,记录下右边界,继续往后遍历,如果s+sum>k,记录下此时s对应的长度。然后 l 右移,此时在s中减去arr[ l ]的值,继续向后遍历,直至结束
参考代码如下:
public static int len3(int[] arr,int k) {
int len=0;
int[] sum=new int[arr.length];//以i开头的最小值
HashMap<Integer, Integer> emap=new HashMap<>();//以i开头的右边界(i,右边界)
sum[arr.length-1]=arr[arr.length-1];
emap.put(arr.length-1, arr.length-1);
for(int i=arr.length-2;i>=0;i--){
if (sum[i+1]<0) {
sum[i]=sum[i+1]+arr[i];
emap.put(i, emap.get(i+1));
}else{
sum[i]=arr[i];
emap.put(i,i);
}
}
int s=0;//最长的和
int end=0;//右边界
for(int i=0;i<arr.length-1;i++){
while(end<arr.length&&s+sum[end]<=k){
//逐步向后加直至到结尾或者大于k
s+=sum[end];
end=emap.get(end)+1;
}
//下一轮循环应该从i+1开始,但是不需要重新计算,直接将s中去掉arr[i]即可
s-=end>i?arr[i]:0;
len=Math.max(len, end-i);
end=Math.max(end, i+1);
}
return len;
}