O(N)求数组中小于等于K的最大子数组长度
1、先介绍O(NlogN)解法,虽然好像没什么相关。
对数组arr,要求最大子数组长度,我们可以先求以每一个位置结尾的符合条件的最大子数组长度,再取最大的。
先生成辅助数组helpArr,其中helpArr[i]表示arr[0~i]上所有数的累加和。
对于位置i,只要求得helpArr中,出现>= sum - K的最早位置j, 则j + 1到i 即为位置i结尾的满足条件的最大子数组。
问题转化为:如何在O(logN)求得数组中,最早出现大于sum - K的数的位置。
将其转化为单调不减数组,因为只要求出现某数的最早位置,故之后只有比该数大的数的位置才有意义。
举个栗子:
helpArr = {0 1 3 2 7 5} → newArr = {0 1 3 3 7 7}
求newArr的最早出现数K的位置显然能用二分法解决。
public static int maxLength(int[] arr, int k) { int[] h = new int[arr.length + 1]; int sum = 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; int len = 0; for (int i = 0; i != arr.length; i++) { sum += arr[i]; pre = getLessIndex(h, sum - k); len = pre == -1 ? 0 : i - pre + 1; res = Math.max(res, len); } return res; } public static int getLessIndex(int[] arr, int num) { int low = 0; int high = arr.length - 1; int mid = 0; int res = -1; while (low <= high) { mid = (low + high) / 2; if (arr[mid] >= num) { res = mid; high = mid - 1; } else { low = mid + 1; } } return res; }
2、 O(N)的解法
先生成辅助数组h, 其中h[i] 表示以arr[i] 开始往右走,最小的累加和。
ends[i] 表示对位置i,其最小累加和的结束位置。
那么,我们从i = 0 位置开始,判断当前位置的最小累加和是否<= K。若满足条件,往下接着结算。
举个栗子:
0 。。。i i+1 。。。。j j+1。。。。x x+1
从0开始的累加和<= K,i是0位置开始最小累加和结束的位置。接下去要看能否往下接着技术。需要看i+1的情况。
若0~i的累加和sum,再加上i+1的累加和<=K,则还可以在延长一段距离到 j。接着要看j+1的情况。
若前面的sum 再加上j+1的累加和不满足条件,则终止。
此时可以结算以0开头的满足条件的最长子数组。
接着计算下一个位置1。
public static int getMaxLen(int[] arr, int k){ int[] h = new int[arr.length]; int[] ends = new int[arr.length]; h[arr.length - 1] = arr[arr.length - 1]; ends[arr.length - 1] = arr.length - 1; for(int i = arr.length - 2; i >= 0; i--){ h[i] = h[i + 1] < 0 ? arr[i] + h[i+1] : arr[i]; if(h[i+1] > 0){ h[i] = arr[i]; ends[i] = i; }else { h[i] = arr[i] + h[i + 1]; ends[i] = ends[i + 1]; } } int end = 0; // 右边界 int res = 0; // 窗口累加和 int sum = 0; for(int i = 0; i < arr.length; i++){ while(end < arr.length && sum + h[end] <= k){ sum += h[end]; end = ends[end] + 1; } sum -= end > i ? arr[i] : 0; //若end根本没动,sum减去arr[i],表示从i+1位置开始。sum无需清零,因为只需要更新几个值就可以复用。 res = Math.max(res, end - i); end = Math.max(end, i + 1); } return res; }