数组累加和三连2:arr可小于等于大于0,请问累加和为k的子数组最大长度是多少?
提示:之前见过数组累加和的另外三个重要题目,动态规划,可以认为是数组累加和456连
之前数组三连的第1连:
【1】数组累加和三连1:arr全大于0,请问累加和为k的子数组最大长度是多少
重要的数组累加和456连:
【4】数组arr中必须以i位置结尾的子数组,其最大累加和是多少?
【5】数组arr的0–i范围上任选一个子数组的最大累加和是多少?
【6】给定数组arr和k,求3个不重叠的长为k的子数组的累加和之和最大值是多少?
其中,4为5的基础,5又是6的基础,一定学仔细了
今天要讲得典型数组累加和三连的解法
今天讲数组累加和三中最简单的1连,2连
题目
数组累加和三连2:arr可小于等于大于0,请问累加和为k的子数组最大长度是多少?
一、审题
示例:arr=5 -1 4 -3 0 3
k=0
单独0可以,长度为1
-1 4 -3可以,长度为3
但是你看,其实 -1 4 -3 0 要也行啊
长度为4
所以,这跟数组三连1不一样,那边arr>0
这里arr随意,没有单调性了
求取结果没有单调性,考虑以arr的i位置结尾的情况
既然没有单调性,那就考虑累加和以i位置结尾的情况
咱们首先准备一个数组的前缀累加和sum
arr = 5 -1 4 -3 0 3
sum=0 5 4 8 5 5 8
位置=-1 0 1 2 3 4 5
代表0–i位置的累加和
0是最开始累加和为0,对应位置是-1
我们为什么要准备前缀累加和数组?
事情是这样的
想要得到任意范围内L–R的累加和
则只需要sum[R]-sum[L-1]
这个知识点,我讲过多次了
所以呢,令j=R-1的话,让L–R内累加和为k时
sum[R]-sum[j]=k
故sum[j]=sum[R]-k
咱们寻找此前sum记录的累加和为sum[R]-k的那个j位置,R-j可不就是这个子数组累加和为k的长度吗?
举个例子:
sum[i]=1000
曾经有一个sum[j]=800
现在k=200
显然R-j必然就是L–R的长度,看下图
因为800+k=1000
所以呢?
咱们的解题流程:
(0)每一个R从0–N-1,都做一次结尾,找到的最大长度更新在max中
(1)求过的累加和,咱们找个哈希表map,把sum[j],和j存下来,
(2)在后续求sum[R]过程中,去map里面搜索是否有一个sum[j]=sum[R]-k的出现过,有的话,拿到那个j,R-j就是咱们要的L–R的长度,更新给max。
思想很独特,每个i做一次结尾,找到前面曾经出现过的sum-k那个位置,L–R就是长度,更新给max即可
注意map中这个j位置,是最早出现的j,它使得sum[j]=sum[R]-k,
如果后续还要别的j让sum[j]=sum[R]-k,那后面这些j,是不要熬存入map中的
保证i-j最大,j尽可能小,那存入map中的就是最早出现的j,它很小。
//复习数组三连2
public static int sumArrk(int [] arr, int k){
if (arr == null || arr.length == 0) return 0;
int N = arr.length;
HashMap<Integer, Integer> map = new HashMap<>();//key:sum,value:j
//累加和从前往后累加给sum就行,不用单独搞数组了
//(0)每一个R从0--N-1,都做一次结尾,找到的最大长度更新在max中
int sum = 0;
int max = 0;//结果
map.put(0, -1);//期初sum=0时,出现的位置-1,方便后面R-j求长度
for (int i = 0; i < N; i++) {//R=i
sum += arr[i];//每个i都累加
//(2)在后续求sum[R]过程中,去map里面搜索是否有一个sum[j]=sum[R]-k的出现过,
// 有的话,拿到那个j,R-j就是咱们要的L--R的长度,更新给max。
if (map.containsKey(sum - k)) max = Math.max(max, i - map.get(sum - k));
//(1)求过的累加和,咱们找个哈希表map,把sum[j],和j存下来,
//sum没加入过map,加一下
if (!map.containsKey(sum)) map.put(sum, i);//i位置记下
}
//每个i都结尾试了一下,max就有结果
return max;
}
public static void test(){
int[] arr = {5,6,4,-3,0,3};
int k = 10;
System.out.println(getMaxLen(arr, k));
System.out.println(sumArrk(arr, k));
}
public static void main(String[] args) {
test();
}
注意: map.put(0, -1);最开始,sum=0时,出现的位置-1,方便后面R-j求长度
万一你碰到这种
-3 3 7
k=7
这种
你不能搞7,长度1
因为-3 3 7就是最长长度
但是曾经map应该放0位置为-1
最早出现的0,那个-1位置,才是我们要的j【而且,有过0了,就不能放新的j了】
这样R-j=2-(-1)=3个,这是对的
如果你不放,那等j=1时,sum=0
你找到的R-j=2-1=1,不行的
测试一波!
5
5
如果你map不提前放0的-1位置试试:错误的
//复习数组三连2
public static int wrongSumArrk(int [] arr, int k){
if (arr == null || arr.length == 0) return 0;
int N = arr.length;
HashMap<Integer, Integer> map = new HashMap<>();//key:sum,value:j
//累加和从前往后累加给sum就行,不用单独搞数组了
//(0)每一个R从0--N-1,都做一次结尾,找到的最大长度更新在max中
int sum = 0;
int max = 0;//结果
//map.put(0, -1);//期初sum=0时,出现的位置-1,方便后面R-j求长度
for (int i = 0; i < N; i++) {//R=i
sum += arr[i];//每个i都累加
//(2)在后续求sum[R]过程中,去map里面搜索是否有一个sum[j]=sum[R]-k的出现过,
// 有的话,拿到那个j,R-j就是咱们要的L--R的长度,更新给max。
if (map.containsKey(sum - k)) max = Math.max(max, i - map.get(sum - k));
//(1)求过的累加和,咱们找个哈希表map,把sum[j],和j存下来,
//sum没加入过map,加一下
if (!map.containsKey(sum)) map.put(sum, i);//i位置记下
}
//每个i都结尾试了一下,max就有结果
return max;
}
public static void test(){
int[] arr = {5,6,4,-3,0,3};
int[] arr2 = {-3,3,7};
int k = 10;
int k2 = 7;
System.out.println(getMaxLen(arr, k));
System.out.println(sumArrk(arr2, k2));
System.out.println(wrongSumArrk(arr2, k2));
}
public static void main(String[] args) {
test();
}
3
1
是吧,不对的
总结
提示:重要经验:
1)数组累加和三连2,当遇到累加和压根和子数组的长度不是单调关系时,考虑子数组以i结尾的情况,这时候就需要用前缀累加和数组了。
2)当哈希表map中有一个最早j位置的sum等于sum[i]-k时,L–R上累加和为k的长度就应该是i-j
3)笔试求AC,可以不考虑空间复杂度,但是面试既要考虑时间复杂度最优,也要考虑空间复杂度最优。