题目
给定一个整数数组(无序且有正有负)和一个目标值,求这个数组中子数组的和为目标值的子数组的最大长度。
思路
子数组是连续的,s[i]表示从0i位置的累加和,s[j]表示0j位置的累加和,那么从j+1~i位置的累加和为s[j]-s[i],他可以表示任意子数组的累加和,如果这个累加和就是目标值aim,那么找出长度最长的即可。
遍历一遍数组,并求出各个位置的累加和,如在i位置时求得为s[i],在此我们是否可以得到在0-i之间的所有子数组中是否存在累加和为aim的,只需要查询下s[i]-aim 这个值是否在之前计算累加和的过程中出现过。
代码
/*
* 给定一个全是正数的数组arr, 和一个整数aim, 求累加和为aim的最大子数组长度
* 基础:子数组是数组中连续的n个元素
*/
public static int getMaxLength(int[] arr ,int aim){
if(arr==null||arr.length==0)
return 0;
int sum=0;
int len=0;
//sum记录从0到i位置处的连续累加和,哈希表记录以value下标结尾从0开始的累加和key
HashMap<Integer,Integer> map=new HashMap<Integer, Integer>();
//sum [0----i] aim [j+1-----i] sum-aim [0---j] 要求的数从j+1开始,当j+1=0时,即从开头开始,j=-1;
//不加任何一个数时,累加和为0,此时下标为-1,就表示还没有添加任何数
map.put(0, -1);
for(int i=0;i<arr.length;i++){
sum+=arr[i];
if(map.containsKey(sum-aim)){//判断哈希表中现在是否存在sum-aim,如果存在得到他的下标j,那么从j+1到i就是子数组和为aim
//记录的下标应该为最早出现的j 后面再出现不更新(因为要求最大长度)
len=Math.max(i-map.get(sum-aim), len);
}
//在后续遍历过程中如果出现了相同的sum,不更新
if(!map.containsKey(sum))//该处的sum值是第一次出现,后面再出现的话不更新(因为要求最大长度)
map.put(sum, i);
}
return len;
}
如果给定的数组全是正整数,那么这道题也可以用滑动窗口的方法解决。滑动窗口法要求在右界固定的情况下,求最大的左界,即通常用来求满足条件的最短子数组。但是由于数组全是正整数,在右界固定的条件下,如果存在满足条件的左界,那这个左界是唯一的。因此使用滑动窗口法可找到所有和等于aim的子数组,从中找出最长的那个就行。
最开始sum=0,right=0,sum一直加arr[right],直到sum>=aim。当sum大于等于aim时,减去arr[left]。
int maxSubArrayLen(int aim, int[] arr) {
if(arr==null||arr.length==0||aim<0)
return 0;
int sum=arr[0];
int len=0;
int left=0;
int right=0; //sum 为left -right 之间的数之和
while(right<arr.length){
if(sum==aim){
len=Math.max(len, right-left+1);
sum-=arr[left++];
}
else if(sum<aim){
right++;
if(right==arr.length)
break;
sum+=arr[right];
}
else {
sum -= arr[left++];
}
}
return len;
}