leetcode中等1508子数组和排序后的区间和

Posted qq_40707462

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了leetcode中等1508子数组和排序后的区间和相关的知识,希望对你有一定的参考价值。

给你一个数组 nums ,它包含 n 个正整数。你需要计算所有非空连续子数组的和,并将它们按升序排序,得到一个新的包含 n * (n + 1) / 2 个数字的数组。

请你返回在新数组中下标为 left 到 right (下标从 1 开始)的所有数字和(包括左右端点)。由于答案可能很大,请你将它对 10^9 + 7 取模后返回。

示例 1:

输入:nums = [1,2,3,4], n = 4, left = 1, right = 5
输出:13 
解释:所有的子数组和为 1, 3, 6, 10, 2, 5, 9, 3, 7, 4 。将它们升序排序后,我们得到新的数组 [1, 2, 3, 3, 4, 5, 6, 7, 9, 10] 。下标从 le = 1 到 ri = 5 的和为 1 + 2 + 3 + 3 + 4 = 13

思路1:暴力

时间复杂度:O(n^2 log n)
空间复杂度:O(n^2)

class Solution 
    public int rangeSum(int[] nums, int n, int left, int right) 
        final int MODULO = 1000000007;
        int sumsLength = n * (n + 1) / 2;
        int[] sums = new int[sumsLength];
        int index = 0;
        for (int i = 0; i < n; i++) 
            int sum = 0;
            for (int j = i; j < n; j++) 
                sum += nums[j];
                sums[index++] = sum;
            
        
        Arrays.sort(sums);
        int ans = 0;
        for (int i = left - 1; i < right; i++) 
            ans = (ans + sums[i]) % MODULO;
        
        return ans;
    

思路2:归并

参考 378、有序矩阵中第 K 小的元素【中等】
例如:

nums = [3, 4, 1, 2, 5, 6]
[3, 7, 8, 10, 15, 21]
[4, 5, 7, 12, 18]
[1, 3, 8, 14]
[2, 7, 13]
[5, 11]
[6]
class Solution 
    public int rangeSum(int[] nums, int n, int left, int right) 
        final int MOD = 1000000007;
        PriorityQueue<int[]>pq=new PriorityQueue<>((int[]a,int[]b)->a[0]-b[0]);
        for(int i=0;i<n;i++)
            pq.offer(new int[]nums[i],i);//i表示第几行
        
        int ans=0,count=0;
        while(++count<=right && !pq.isEmpty())
            int[]cur=pq.poll();
            if(count>=left) ans += cur[0] % MOD;
            if(cur<n-1) pq.offer(new int[]cur[0]+nums[cur[1]+1], cur[1]+1);
        
        return res;
    

思路3:二分(懒得看了)

  • 构造两个数组,第一个数组 prefixSums 存储原始数组的前缀和,第二个数组 prefixPrefixSums 存储第一个数组的前缀和。

  • 找出 prefixSums 前 left 个元素和,以及前 right 个元素和,相减,即prefixPrefixSums[right]-prefixPrefixSums[left]。可转化为找出「有序矩阵中第K小的元素」

对于从 0 到 n−1 的每个下标 i,找到最大的下标 j ,使得原始数组从[i ,j−1] 范围的子数组的元素之和不超过目标值,由于原始数组的元素都是正整数,可以能得到 j−i 个和不超过目标值的子数组。遍历完 i 的所有可能取值之后,即可知道有多少个子数组的和不超过目标值。目标值的最终取值即为第 k 小的子数组的和。

得到第 k 小的子数组的和之后,即可求所有的子数组的和当中的最小的 k 个值之和。令第 k 小的子数组的和是 num,考虑到可能有多个子数组的和都等于第 k 小的子数组的和,因此分成两步计算。

第一步计算所有严格小于num 的子数组的和的个数以及它们的和,假设有 count 个严格小于 num 的子数组的和,它们的和是 sum,可以借助构造的两个数组,使用双指针计算得到 count 和 sum 的值。

具体做法是,对于从 0 到 n−1 的每个下标 i,找到最大的下标 j 使得原始数组从下标 i 到下标 j-1 范围的子数组的元素之和不超过num,能得到 j-i个和不超过目标值的子数组,这些子数组的和之和计算如下:

prefixPrefixSums[j]−prefixPrefixSums[i]−prefixSums[i]×(j−i)
(  prefixSums [i+1,j]内的所有元素之和  )
class Solution 
    static final int MODULO = 1000000007;

    public int rangeSum(int[] nums, int n, int left, int right) 
        int[] prefixSums = new int[n + 1];
        prefixSums[0] = 0;
        for (int i = 0; i < n; i++) 
            prefixSums[i + 1] = prefixSums[i] + nums[i];
        
        int[] prefixPrefixSums = new int[n + 1];
        prefixPrefixSums[0] = 0;
        for (int i = 0; i < n; i++) 
            prefixPrefixSums[i + 1] = prefixPrefixSums[i] + prefixSums[i + 1];
        
        return (getSum(prefixSums, prefixPrefixSums, n, right) - getSum(prefixSums, prefixPrefixSums, n, left - 1)) % MODULO;
    

    public int getSum(int[] prefixSums, int[] prefixPrefixSums, int n, int k) 
        int num = getKth(prefixSums, n, k);
        int sum = 0;
        int count = 0;
        for (int i = 0, j = 1; i < n; i++) 
            while (j <= n && prefixSums[j] - prefixSums[i] < num) 
                j++;
            
            j--;
            sum = (sum + prefixPrefixSums[j] - prefixPrefixSums[i] - prefixSums[i] * (j - i)) % MODULO;
            count += j - i;
        
        sum = (sum + num * (k - count)) % MODULO;
        return sum;
    

    public int getKth(int[] prefixSums, int n, int k) 
        int low = 0, high = prefixSums[n];
        while (low < high) 
            int mid = (high - low) / 2 + low;
            int count = getCount(prefixSums, n, mid);
            if (count < k) 
                low = mid + 1;
             else 
                high = mid;
            
        
        return low;
    

    public int getCount(int[] prefixSums, int n, int x) 
        int count = 0;
        for (int i = 0, j = 1; i < n; i++) 
            while (j <= n && prefixSums[j] - prefixSums[i] <= x) 
                j++;
            
            j--;
            count += j - i;
        
        return count;
    

以上是关于leetcode中等1508子数组和排序后的区间和的主要内容,如果未能解决你的问题,请参考以下文章

leetcode-5445- 子数组和排序后的区间和

leetcode中等2080区间内查询数字的频率

leetcode中等2080区间内查询数字的频率

解题报告力扣 第 269 场周赛

Leetcode题目215.数组中的第K个最大元素(中等)

leetcode 53. Maximum Subarray 最大子数组和(中等)