Hard
题目描述
数组的得分定义为数组元素的总和乘以数组的长度。
例如,数组 [1, 2, 3, 4, 5] 的得分为 (1 + 2 + 3 + 4 + 5) * 5 = 75。
给定一个正整数数组 nums 和一个整数 k,返回 nums 中得分严格小于 k 的非空子数组的数目。
子数组是数组中连续的元素序列。
示例 1:
输入:nums = [2,1,4,3,5], k = 10
输出:6
解释:
得分小于 10 的 6 个子数组为:
- [2] 得分为 2 * 1 = 2
- [1] 得分为 1 * 1 = 1
- [4] 得分为 4 * 1 = 4
- [3] 得分为 3 * 1 = 3
- [5] 得分为 5 * 1 = 5
- [2,1] 得分为 (2 + 1) * 2 = 6
注意像 [1,4] 和 [4,3,5] 这样的子数组不被考虑,因为它们的得分分别是 10 和 36,而我们需要得分严格小于 10。
示例 2:
输入:nums = [1,1,1], k = 5
输出:5
解释:
除了 [1,1,1] 之外的每个子数组得分都小于 5。
[1,1,1] 的得分为 (1 + 1 + 1) * 3 = 9,大于 5。
因此,有 5 个得分小于 5 的子数组。
约束条件:
1 <= nums.length <= 10^51 <= nums[i] <= 10^51 <= k <= 10^15
解题思路
这道题要求计算得分小于 k 的子数组数目,其中得分定义为子数组元素和乘以子数组长度。
核心思路:
暴力解法:枚举所有可能的子数组,计算每个子数组的得分并统计符合条件的数量。时间复杂度为 O(n³),对于大规模数据会超时。
滑动窗口优化:使用双指针技术,维护一个滑动窗口 [left, right]。关键观察是:
- 如果子数组 [left, right] 的得分 >= k,那么所有包含这个子数组的更长子数组的得分也会 >= k
- 因此可以用双指针维护最大的符合条件的窗口
算法流程:
- 使用 left 和 right 两个指针,维护当前窗口的元素和 sum
- 当窗口 [left, right] 的得分 >= k 时,收缩左边界
- 当窗口 [left, right] 的得分 < k 时,以 right 为右端点的所有子数组 [left, right], [left+1, right], …, [right, right] 都满足条件
- 每次 right 右移时,新增的满足条件的子数组数量为 (right - left + 1)
推荐解法:滑动窗口 时间复杂度 O(n),空间复杂度 O(1),是最优解法。
代码实现
class Solution {
public:
long long countSubarrays(vector<int>& nums, long long k) {
int n = nums.size();
long long result = 0;
long long sum = 0;
int left = 0;
for (int right = 0; right < n; right++) {
sum += nums[right];
// 当前窗口得分 >= k 时,收缩左边界
while (sum * (right - left + 1) >= k) {
sum -= nums[left];
left++;
}
// 以 right 为右端点的符合条件的子数组数量
result += right - left + 1;
}
return result;
}
};
class Solution:
def countSubarrays(self, nums: List[int], k: int) -> int:
n = len(nums)
result = 0
sum_val = 0
left = 0
for right in range(n):
sum_val += nums[right]
# 当前窗口得分 >= k 时,收缩左边界
while sum_val * (right - left + 1) >= k:
sum_val -= nums[left]
left += 1
# 以 right 为右端点的符合条件的子数组数量
result += right - left + 1
return result
public class Solution {
public long CountSubarrays(int[] nums, long k) {
int n = nums.Length;
long result = 0;
long sum = 0;
int left = 0;
for (int right = 0; right < n; right++) {
sum += nums[right];
// 当前窗口得分 >= k 时,收缩左边界
while (sum * (right - left + 1) >= k) {
sum -= nums[left];
left++;
}
// 以 right 为右端点的符合条件的子数组数量
result += right - left + 1;
}
return result;
}
}
var countSubarrays = function(nums, k) {
const n = nums.length;
let result = 0;
let sum = 0;
let left = 0;
for (let right = 0; right < n; right++) {
sum += nums[right];
// 当前窗口得分 >= k 时,收缩左边界
while (sum * (right - left + 1) >= k) {
sum -= nums[left];
left++;
}
// 以 right 为右端点的符合条件的子数组数量
result += right - left + 1;
}
return result;
};
复杂度分析
| 复杂度类型 | 滑动窗口解法 |
|---|---|
| 时间复杂度 | O(n) |
| 空间复杂度 | O(1) |
说明:
- 时间复杂度:每个元素最多被访问两次(一次被右指针访问,一次被左指针移出),因此总时间复杂度为 O(n)
- 空间复杂度:只使用了常数个额外变量,空间复杂度为 O(1)
相关题目
. Maximum Subarray (Medium)
. Subarray Product Less Than K (Medium)
. Binary Subarrays With Sum (Medium)