1918. 第 K 小的子数组和 🔒
题目描述
给你一个 长度为 n
的整型数组 nums
和一个数值 k
,返回 第 k
小的子数组和。
子数组 是指数组中一个 非空 且不间断的子序列。 子数组和 则指子数组中所有元素的和。
示例 1:
输入: nums = [2,1,3], k = 4 输出: 3 解释: [2,1,3] 的子数组为: - [2] 和为 2 - [1] 和为 1 - [3] 和为 3 - [2,1] 和为 3 - [1,3] 和为 4 - [2,1,3] 和为 6 最小子数组和的升序排序为 1, 2, 3, 3, 4, 6。 第 4 小的子数组和为 3 。
示例 2:
输入:nums = [3,3,5,5], k = 7 输出:10 解释:[3,3,5,5] 的子数组为: - [3] 和为 3 - [3] 和为 3 - [5] 和为 5 - [5] 和为 5 - [3,3] 和为 6 - [3,5] 和为 8 - [5,5] 和为 10 - [3,3,5], 和为 11 - [3,5,5] 和为 13 - [3,3,5,5] 和为 16 最小子数组和的升序排序为 3, 3, 5, 5, 6, 8, 10, 11, 13, 16。第 7 小的子数组和为 10 。
提示:
n == nums.length
1 <= n <= 2 * 104
1 <= nums[i] <= 5 * 104
1 <= k <= n * (n + 1) / 2
解法
方法一:二分查找 + 双指针
我们注意到,题目中数组元素均为正整数,子数组的和 \(s\) 越大,那么数组中子数组和小于等于 \(s\) 的个数就越多。这存在一个单调性,因此我们可以考虑使用使用二分查找的方法来求解。
我们二分枚举子数组的和,初始化左右边界分别为数组 \(nums\) 中的最小值以及所有元素之和。每次我们计算数组中子数组和小于等于当前枚举值的个数,如果个数大于等于 \(k\),则说明当前枚举值 \(s\) 可能是第 \(k\) 小的子数组和,我们缩小右边界,否则我们增大左边界。枚举结束后,左边界即为第 \(k\) 小的子数组和。
问题转换为计算一个数组中,有多少个子数组的和小于等于 \(s\),我们可以通过函数 \(f(s)\) 来计算。
函数 \(f(s)\) 的计算方法如下:
- 初始化双指针 \(j\) 和 \(i\),分别指向当前窗口的左右边界,初始时 \(j = i = 0\)。初始化窗口内元素的和 \(t = 0\)。
- 用变量 \(cnt\) 记录子数组和小于等于 \(s\) 的个数,初始时 \(cnt = 0\)。
- 遍历数组 \(nums\),每次遍历到一个元素 \(nums[i]\),我们将其加入窗口,即 \(t = t + nums[i]\)。如果此时 \(t \gt s\),我们需要不断地将窗口的左边界右移,直到 \(t \le s\) 为止,即不断地执行 \(t -= nums[j]\),并且 \(j = j + 1\)。接下来我们更新 \(cnt\),即 \(cnt = cnt + i - j + 1\)。继续遍历下一个元素,直到遍历完整个数组。
最后将 \(cnt\) 作为函数 \(f(s)\) 的返回值。
时间复杂度 \(O(n \times \log S)\),空间复杂度 \(O(1)\)。其中 \(n\) 为数组 \(nums\) 的长度,而 \(S\) 为数组 \(nums\) 中所有元素之和。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|