子数组累加和系列问题
题目
给定一个无序数组arr,其中元素可正、可负、可0,再给定一个整数k,求arr所有的子数组中累加和为k的最长子数组长度。
思路
如果暴力的算就是遍历所有子数组,求所有和为k的长度,求出最大,时间复杂度是o(n*n)
还可以用sum表示所有以i结尾的数组的和,如果sum[i]-k=sum[j],则说明(j,i]的区间上的和是k
那么可以用hashMap记录下所有出现的和的第一次,然后每次进行比较则可以算出
时间复杂度是O(n)
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public static int getKLen(int[] arr, int k){ //key表示和的数值,value表示第一次出现该值的位置 //该map只记录第一次出现的情况 HashMap<Integer, Integer> mymap = new HashMap<Integer, Integer>(); //因为和0的记录是所有值都不取,不会被自动加入 //因此这里添加,表示从开头是选 mymap.put(0, -1); int sum = 0; int len = 0; for (int i = 0; i < arr.length; i++) { //以i结尾的和 sum += arr[i]; //只记录第一次出现的和,因为这样才能最长 if(!mymap.containsKey(sum)){ mymap.put(sum, i); } //如果有sum[j]+k=sum[i],说明(j,i]的部分和是k if(mymap.containsKey(sum - k)){ int j = mymap.get(sum - k); //以j结尾的和和以i结尾的和,期间的长度是i-j len = Math.max(len, i - j); } } return len; }
|
题目
给定一个数组arr,该数组无序,但每个值均为正数,再给定一个正数k。
求arr的所有子数组中所有元素相加和为k的最长子数组长度。例如,arr=[1,2,1,1,1],k=3。
累加和为3的最长子数组为[1,1,1],所以结果返回3。
要求:时间复杂度O(N),额外空间复杂度O(1)。
思路
该题目遇上一题的区别就是正数
因此该题目可以用一个区间来找和是k的组合
- left区间的左端
- right区间的右端
- sum该区间的和
- len最大的长度
开始left和right都指向0,一边计算sum一边更新len
- sum > k right右移,继续增加区间的长度
- sum < k left右移,减少区间元素,使得和减小
- sum == k left右移,等于之后要更新len,为了检测是否有更多和为k的子数组,要继续遍历
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| public static int getMaxKLen(int[] a,int k){ if(a.length < 0 || k <= 0 || a == null){ return 0; } int left = 0; int right = 0; //这里sum已经设置了,后面要先改区间,再算和 int sum = a[0]; int len = 0; while(left < a.length && right < a.length) { if(sum < k){ //先改区间,再算和 right ++; if (right == a.length) { break; } sum += a[right]; }else if(sum > k){ //先改区间,再算和 left++; sum -= a[left]; }else{ //sum== k len = Math.max(len, right - left + 1); left++; sum -= a[left]; } } return len; }
|
题目
给定一个无序数组arr,其中元素可正、可负、可0,给定一个整数 k。
求arr所有的子数组中累加和小于或等于k的最长子数组长度。
例如:arr=[3,-2,-4,0,6],k=-2,相加和小于或等于-2的最长子数组为{3,-2,-4,0},所以结果返回4
思路
回顾上面的题目,用一个数组记录当前位置所有和,然后一边遍历,一边算现在的和,
当遍历到i时,用x=当前和-k,找所有和中是否有x值,有就说明该位置到当前位置和是k
而本题目是所求的和小于等于k值,对于等式x=当前和-k,这里k<=aa,因此x>=和-aa,而遍历的过程是寻找数组中大于等于和-a[i]的值
对于一个序列,求最长则需要找到第一个大于等于的值
因此辅助数组改为记录当前位置和的最大值,是一个递增序列
例如[-2,4,2,0,2,1]求第一个大于等于2的位置,那么就是4的位置
因此序列改为[-2,4,4,4,4],这样对于递增序列,可以用二分降到O(log(n))
整体的复杂度为O(n*log(n))
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| public static int maxLength(int[] arr, int k) { // 记录当前位置出现的最大和,是一个递增数组 int[] h = new int[arr.length + 1]; int sum = 0; // 加入和是0的记录 h[0] = sum; for (int i = 0; i != arr.length; i++) { sum += arr[i]; h[i + 1] = Math.max(sum, h[i]); } sum = 0; // 结果 int res = 0; // 满足区间的左边界 int pre = 0; // 长度,右边界-左边界+1 int len = 0; for (int i = 0; i != arr.length; i++) { // 当前所有的和 sum += arr[i]; // k+x=sum // 求小于等于k的区间,那么就要找到大于等于x即(sum-k)的值 // 即求出第一个大于x的位置 pre = getIndex(h, sum - k); // 可能找不到 len = pre == -1 ? 0 : i - pre + 1; res = Math.max(res, len); } return res; } // 第一个大于x的位置R public static int getIndex(int[] a, int x) { int left = 0; int right = a.length; int mid = 0; // 有可能出现找不到的情况 int res = -1; // 区间是[0,n) // 分支中出现了l=mid 或者r=mid,循环条件要写成left < right,否则写成left <= right // 避免出现left=right-1出现死循环 while (left < right) { mid = (left + right) / 2; // 第一个大于x的元素,没有等号 if (a[mid] > x) { // 找不到的情况是x比任何数都大,因此如果存在都会进左区间 // 右区间找不到情况下是不进入的 // 用res来记录,如果找到不进入此,不改变值,返回-1 res = mid; // 在区间[left,mid)找 right = mid; } else { // 在区间[mid+1,right)找 left = mid + 1; } } // 找不到 return res; }
|