加载中...
1508-子数组和排序后的区间和(Range Sum of Sorted Subarray Sums)
发表于:2021-12-03 | 分类: 中等
字数统计: 1.8k | 阅读时长: 8分钟 | 阅读量:

原文链接: https://leetcode-cn.com/problems/range-sum-of-sorted-subarray-sums

英文原文

You are given the array nums consisting of n positive integers. You computed the sum of all non-empty continuous subarrays from the array and then sorted them in non-decreasing order, creating a new array of n * (n + 1) / 2 numbers.

Return the sum of the numbers from index left to index right (indexed from 1), inclusive, in the new array. Since the answer can be a huge number return it modulo 109 + 7.

 

Example 1:

Input: nums = [1,2,3,4], n = 4, left = 1, right = 5
Output: 13 
Explanation: All subarray sums are 1, 3, 6, 10, 2, 5, 9, 3, 7, 4. After sorting them in non-decreasing order we have the new array [1, 2, 3, 3, 4, 5, 6, 7, 9, 10]. The sum of the numbers from index le = 1 to ri = 5 is 1 + 2 + 3 + 3 + 4 = 13. 

Example 2:

Input: nums = [1,2,3,4], n = 4, left = 3, right = 4
Output: 6
Explanation: The given array is the same as example 1. We have the new array [1, 2, 3, 3, 4, 5, 6, 7, 9, 10]. The sum of the numbers from index le = 3 to ri = 4 is 3 + 3 = 6.

Example 3:

Input: nums = [1,2,3,4], n = 4, left = 1, right = 10
Output: 50

 

Constraints:

  • n == nums.length
  • 1 <= nums.length <= 1000
  • 1 <= nums[i] <= 100
  • 1 <= left <= right <= n * (n + 1) / 2

中文题目

给你一个数组 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 。

示例 2:

输入:nums = [1,2,3,4], n = 4, left = 3, right = 4
输出:6
解释:给定数组与示例 1 一样,所以新数组为 [1, 2, 3, 3, 4, 5, 6, 7, 9, 10] 。下标从 le = 3 到 ri = 4 的和为 3 + 3 = 6 。

示例 3:

输入:nums = [1,2,3,4], n = 4, left = 1, right = 10
输出:50

 

提示:

  • 1 <= nums.length <= 10^3
  • nums.length == n
  • 1 <= nums[i] <= 100
  • 1 <= left <= right <= n * (n + 1) / 2

通过代码

高赞题解

相关题目

378. 有序矩阵中第 K 小的元素
719. 找出第 k 小的距离对

解题思路

  1. 找出排序后数组的第 $k$ 小的元素。
    至于如何找出,可以参考第 378 题 和 719 题的思路。为此我们首先构造出一个二维有序矩阵。
    定义矩阵的元素 $A(i,j)$:原数组 $nums$ 的从第 $i$ 个元素到第 $j$ 个元素(下标从 1 开始)的和。
    例如:关于题目中的示例 $[1,2,3,4]$,可以构造出如下的矩阵:
    $$\begin{matrix} 1 & 3 & 6 & 10 \ & 2 & 5 & 9 \ & & 3 & 7 \ & & & 4 \end{matrix}$$
    注意: 当 $j < i$ 时,矩阵中是没有元素的。
    显然,这个矩阵 关于列号 $j$ 递增、关于行号 $i$ 递减。 因此我们可以用类似 378 题的思路(二分+双指针)来求排序后数组的第 $k$ 小元素。
    下面是由 $[1,2,3,4,5,6,7,8,9,10]$ 构造出的矩阵的色阶图。假设我们要找所有的 $val < 20$ 的元素数量,我们只需要沿着图中的线走一遍即可。

    深度截图_选择区域_20200717210440.png

    提示: 解题时,我们不需要完整地构造出矩阵,我们只需要随时知道 $A(i,j)$ 即可。为了求出 $A(i,j)$,我们首先求出原数组的前缀和 $sums$,然后根据定义,$A(i,j) = sums[j] - sums[i-1]$。(代码中 $i$ 的下标从 $0$ 开始,因此写成了 $sums[j] - sums[i]$)。

  2. 求出排序后数组的前 $k$ 项和。
    需要考虑一个细节:数组中的第 $k$ 小的元素可能有多个,比如 $…a,a,x,x,[x],x,c,d…$,第 k 小元素为 $x$。
    这时的思路是:先求所有 严格小于 $x$ 的元素和 $S$ 和 元素个数 $cnt$,然后再求等于 $x$ 的部分。

    为求出元素和 $S$, 我们先构造 “前缀和的前缀和” (即首行元素的前缀和 $ssums$)以供参考。
    以示例 $[1,2,3,4]$ 所构造出的矩阵为例,
    $$\begin{matrix} \textbf{ssums}: & \textbf{1} & \textbf{4} & \textbf{10} & \textbf{20} \ array:& 1 & 3 & 6 & 10 \ &→ & \textbf{[2} & \textbf{5]} & 9 \ && & 3 & 7 \ && & & 4 \end{matrix}$$
    假设我们需要求矩阵的第 $2$ 行的第 $2 \sim 3$ 列 (下标从 $1$ 开始) 的元素和。
    可以看出规律:待求元素 $2,5$ 和 首行元素 $3,6$ 相比,都差了一个 $1$。
    因此,我们可以先求出 首行 的对应元素的和,然后再减去 $1 \times 元素的个数$:
    $$\begin{aligned} res &= (ssums[3] - ssums[1]) - (3-2+1)\times sums[1] \ &= (10-1) - 2 \&= 7. \end{aligned} $$
    推广到一般情况,求出矩阵中的第 $m$ 行的第 $a \sim b$ 列元素的和(下标从 $1$ 开始)的公式为:
    $$res = (ssums[b] - ssums[a-1]) - (b-a+1) \times sums[m-1].$$

    然后等于 $x$ 的部分呢?这部分答案是 $(k - cnt) \times x$。
    最后的答案就是 $S + (k - cnt) \times x$。

    为了避免溢出,答案和中间结果用 long (64位) 来存储。

  3. 算出答案。
    我们定义 $F(x)$ 为排序后数组的前 $x$ 项和。根据定义,题目的答案就是 $F(right) - F(left-1)$。

    不要忘记取模 1e9 + 7。

代码

image.png

class Solution {
public:
    int getCnt(long sums[], int n, int x) {
        int res = 0;
        for(int i = 0, p = 1; i < n; ++i) {
            while(p <= n && sums[p] - sums[i] <= x)
                ++p;
            res += p-1-i;
        }
        return res;
    }
    int getKth(long sums[], int n, int idx) {
        int l = 0, r = 1e5;
        while(l < r) {
            int mid = (l + r) / 2, cnt = getCnt(sums, n, mid);
            if(cnt < idx)
                l = mid + 1;
            else
                r = mid;
        }
        return l;
    }
    long F(long sums[], long ssums[], int n, int x) {
        long num = getKth(sums, n, x), res = 0, cnt = 0;
        for(int i = 0, p = 1; i < n; ++i) {
            while(p <= n && sums[p] - sums[i] < num)
                ++p;
            // 这里, 需要求出矩阵的第 i+1 行的第 i+1 ~ p-1 (下标从 1 开始)个数字的和
            // 带入公式,有 m = i+1, a = i+1, b = p-1, 得
            // res = ssums[b] - ssums[a-1] + (b-a+1)*sums[m-1]
            //     = ssums[(p-1)] - ssums[(i+1)-1] - (((p-1)-(i+1))+1)*sums[(i+1)-1]
            //     = ssums[p-1] - ssums[i] - (p-i-1)*sums[i].
            res += ssums[p-1] - ssums[i] - sums[i] * ((long)(p-1-i));
            cnt += p-1-i;
        }
        return res + (x-cnt)*num;
    }
    int rangeSum(vector<int>& nums, int n, int left, int right) {
        long sums[n+1]; // 前缀和
        sums[0] = 0;
        for(int i = 0; i < n; ++i)
            sums[i+1] = sums[i] + nums[i];

        long ssums[n+1]; // 前缀和的前缀和
        ssums[0] = 0;
        for(int i = 0; i < n; ++i)
            ssums[i+1] = ssums[i] + sums[i+1];
        
        return (F(sums, ssums, n, right) - F(sums, ssums, n, left-1)) % ((long)(1e9 + 7));
    }
};

统计信息

通过次数 提交次数 AC比率
6298 11387 55.3%

提交历史

提交时间 提交结果 执行时间 内存消耗 语言
上一篇:
1489-找到最小生成树里的关键边和伪关键边(Find Critical and Pseudo-Critical Edges in Minimum Spanning Tree)
下一篇:
1509-三次操作后最大值与最小值的最小差(Minimum Difference Between Largest and Smallest Value in Three Moves)
本文目录
本文目录