归并排序练习(四)— 区间和的个数

题目
给你一个整数数组 nums 以及两个整数 lower 和 upper 。求数组中,值位于范围 [lower, upper] (包含 lower 和 upper)之内的区间和的个数 。

区间和 S(i, j) 表示在 nums 中,位置从 i 到 j 的元素之和,包含 i 和 j (i ≤ j)。

分析

  1. 如果用最暴力的方式就是有多少个子数组都遍历出来,那么将从0 ~ 0,0 ~ 1 ,0 ~ 2,0 ~ N - 1,1 ~ 1 ,1 ~ 2,1 ~ N -1的方式进行遍历,看哪个范围子数组的累加和在【lower,upper】上,此时的时间复杂度是 O ( N 2 ) O(N^2) O(N2) ,那对于每一个从(i,j)的子数组,验证的方式是遍历的话,那整体的时间复杂度就是 O ( N 3 ) O(N^3) O(N3)
  2. 如果能够避免遍历,根据原始数组生成一个前缀和数组的话,每次验证【i - j】范围的上的数的话,就可以直接对前缀和数组进行操作,时间复杂度就可以从 O ( N 3 ) O(N^3) O(N3) O ( N 2 ) O(N^2) O(N2)

转化

  1. 看原数组中已每一个数结尾的子数组(0 - 0 ,0 - 1,1 - 1)是否在【lower,upper】上,而后将求得的数进行累加,就是整个的答案。
  2. 如果原始数组中,【i,j】范围的累加和在 【lower,upper】上,那么【0,j】减 【0,i -1】范围,也一定在【lower,upper】上。
  3. 所以,如果以X结尾的子数组的累加和是50,【lower,upper】的范围是【10,20】,那么就看X之前的子数组有哪一个范围是在【30,40】的,如果有,则一定有子数组的累加和范围在【lower,upper】上。

举例
假设,0 - 10位置结尾的子数组,累加和是50, 给定的【lower,upper】的范围是【10,20】,那么用50 - upper,50 - lower就会得到一个范围是【30,40】,那如果0 -5位置的的子数组累加和是35,在【30,40】这个区间,就可以证明 6 - 10范围上一定存在累加和满足【10,20】区间的数组。

结果
生成前缀和数组,递归并merge,在merge过程中,从右组开始遍历,通过【X - upper,X - lower】每次都重新计算新的【lower,upper】,并判断左组有哪些数在这个心的区间,就代表有多少子数组符合原始的【lower,upper】区间。

代码实现:

public int countRangeSum(int[] nums, int lower, int upper) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        //生成前缀和数组
        long[] sum = new long[nums.length];
        sum[0] = nums[0];

        for (int i = 1; i < nums.length; i++) {
            sum[i] = sum[i - 1] + nums[i];
        }

        return count(sum,0,sum.length -1,lower,upper);
    }


    public static int count(long[] nums, int L, int R, int lower, int upper) {
        // L == R,则看在原始数组中,0 - L范围累加和是否满足在lower 和 upper区间。
        if (L == R) {
            return lower <= nums[L] && nums[L] <= upper ? 1 : 0;
        }

        int mid = L + ((R - L) >> 1);

        return count(nums, L, mid, lower, upper) +
                count(nums, mid + 1, R, lower, upper) +
                merge(nums, L, mid, R, lower, upper);
    }

    public static int merge(long[] nums, int L, int M, int R, int lower, int upper) {
        int windowL = L;
        int windowR = L;
        int ans = 0;
        //从右组遍历
        for (int i = M + 1; i <= R; i++) {
            //
            long max = nums[i] - lower;
            long min = nums[i] - upper;

            while (windowL <= M && nums[windowL] < min){
                windowL++;
            }

            while (windowR <=M && nums[windowR] <= max){
                windowR++;
            }
            ans += windowR - windowL;
        }
        int p1 = L;
        int p2 = M + 1;
        long[] help = new long[R - L + 1];
        int index = 0;

        while (p1 <= M && p2 <= R) {
            help[index++] = nums[p1] <= nums[p2] ? nums[p1++] : nums[p2++];
        }

        while (p1 <= M) {
            help[index++] = nums[p1++];
        }

        while (p2 <= R) {
            help[index++] = nums[p2++];
        }

        for (int j = 0; j < help.length; j++) {
            nums[L + j] = help[j];
        }
        return ans;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值