O(N)求数组中小于等于K的最大子数组长度

Posted 贾斯彼迭

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了O(N)求数组中小于等于K的最大子数组长度相关的知识,希望对你有一定的参考价值。

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;
    }

 

以上是关于O(N)求数组中小于等于K的最大子数组长度的主要内容,如果未能解决你的问题,请参考以下文章

未排序数组中累加和小于或等于给定值的最长子数组长度

算法总结之 未排序数组中累加和小于或等于给定值的最长子数组长度

栈和队列----最大值减去最小值小于等于num的子数组的数量

左神算法书籍《程序员代码面试指南》——1_10最大值减去最小值小于或等于num的子数组数量

1.10 最大值减去最小值小于或等于num的子数组数量

如何找到具有最小k长度和最大和的子数组?