Medium
题目描述
给定一个二进制数组 nums 和一个整数 goal,返回和为 goal 的非空子数组的数量。
子数组是数组的连续部分。
示例 1:
输入: nums = [1,0,1,0,1], goal = 2
输出: 4
解释: 4 个子数组如下所示(用粗体下划线表示):
[1,0,1,0,1]
[1,0,1,0,1]
[1,0,1,0,1]
[1,0,1,0,1]
示例 2:
输入: nums = [0,0,0,0,0], goal = 0
输出: 15
提示:
1 <= nums.length <= 3 * 10^4nums[i]只能是0或10 <= goal <= nums.length
解题思路
这道题要求统计和为目标值的子数组个数,有三种主要解法:
解法一:前缀和 + 哈希表
核心思想是利用前缀和的性质。如果前缀和 prefixSum[j] - prefixSum[i] = goal,那么子数组 nums[i+1...j] 的和就是 goal。我们遍历数组,维护前缀和,并用哈希表记录每个前缀和出现的次数。对于当前位置的前缀和 sum,我们查找 sum - goal 在哈希表中的出现次数,这就是以当前位置为结尾且和为 goal 的子数组个数。
解法二:滑动窗口
由于数组只包含 0 和 1,我们可以用滑动窗口技巧。关键观察是:如果存在和为 goal 的子数组,那么在满足条件的最短子数组基础上,可以通过在左侧添加连续的 0 来得到更多满足条件的子数组。
解法三:数学转换
将问题转换为"和小于等于 goal 的子数组个数" 减去 “和小于等于 goal-1 的子数组个数”。这样可以用单调滑动窗口来解决。
推荐使用前缀和 + 哈希表的解法,思路清晰且容易理解。
代码实现
class Solution {
public:
int numSubarraysWithSum(vector<int>& nums, int goal) {
unordered_map<int, int> prefixCount;
prefixCount[0] = 1; // 前缀和为0的情况
int sum = 0, result = 0;
for (int num : nums) {
sum += num;
if (prefixCount.find(sum - goal) != prefixCount.end()) {
result += prefixCount[sum - goal];
}
prefixCount[sum]++;
}
return result;
}
};
class Solution:
def numSubarraysWithSum(self, nums: List[int], goal: int) -> int:
prefix_count = {0: 1} # 前缀和为0的情况
current_sum = 0
result = 0
for num in nums:
current_sum += num
if current_sum - goal in prefix_count:
result += prefix_count[current_sum - goal]
prefix_count[current_sum] = prefix_count.get(current_sum, 0) + 1
return result
public class Solution {
public int NumSubarraysWithSum(int[] nums, int goal) {
Dictionary<int, int> prefixCount = new Dictionary<int, int>();
prefixCount[0] = 1; // 前缀和为0的情况
int sum = 0, result = 0;
foreach (int num in nums) {
sum += num;
if (prefixCount.ContainsKey(sum - goal)) {
result += prefixCount[sum - goal];
}
if (prefixCount.ContainsKey(sum)) {
prefixCount[sum]++;
} else {
prefixCount[sum] = 1;
}
}
return result;
}
}
var numSubarraysWithSum = function(nums, goal) {
const prefixCount = new Map();
prefixCount.set(0, 1); // 前缀和为0的情况
let sum = 0, result = 0;
for (const num of nums) {
sum += num;
if (prefixCount.has(sum - goal)) {
result += prefixCount.get(sum - goal);
}
prefixCount.set(sum, (prefixCount.get(sum) || 0) + 1);
}
return result;
};
复杂度分析
| 复杂度类型 | 前缀和+哈希表解法 |
|---|---|
| 时间复杂度 | O(n) |
| 空间复杂度 | O(n) |
其中 n 为数组长度。时间复杂度为 O(n) 是因为需要遍历一次数组,每次哈希表操作的平均时间复杂度为 O(1)。空间复杂度为 O(n) 是因为在最坏情况下,哈希表需要存储 n+1 个不同的前缀和。