本文系转载
作者:大鱼
链接:https://leetcode.cn/problems/subarray-sum-equals-k/solutions/1485461/by-da-yu-bt-ge0y/
来源:力扣(LeetCode)
有大佬总结了用模板解决四个《前缀和+哈希表》问题,总结的不错,加上这类题几乎是面试必考题,在这里搬用一下。
leetcode 560.和为 K 的子数组:https://leetcode.cn/problems/subarray-sum-equals-k/
leetcode 1248. 统计「优美子数组」: https://leetcode.cn/problems/count-number-of-nice-subarrays/
leetcode 974.和可被 K 整除的子数组:https://leetcode.cn/problems/subarray-sums-divisible-by-k
leetcode 523. 连续的子数组和:https://leetcode.cn/problems/continuous-subarray-sum/
leetcode 1512. 好数对的数目 https://leetcode.cn/problems/number-of-good-pairs/
leetcode 560.和为 K 的子数组
给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的子数组的个数 。
子数组是数组中元素的连续非空序列。
示例 1:
输入:nums = [1,1,1], k = 2
输出:2
示例 2:
输入:nums = [1,2,3], k = 3
输出:2
提示:
1 <= nums.length <= 2 * 104
-1000 <= nums[i] <= 1000
-107 <= k <= 107
简洁版代码
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
int res = 0, n = nums.size();
for (int i = 0; i < n; ++i) {
int sum = nums[i];
if (sum == k) ++res;
for (int j = i + 1; j < n; ++j) {
sum += nums[j];
if (sum == k) ++res;
}
}
return res;
}
};
前缀和+hash表
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
int res = 0;
int sum = 0;
unordered_map<int,int>umap={{0,1}};
for (int n : nums){
sum += n;
int cur = sum - k;
if(umap.count(cur)) {
res += umap[cur];
}
umap[sum]++;
}
return res;
}
};
以上代码实现了一个函数 subarraySum,用于计算一个整数数组 nums 中所有和为 k 的子数组的数量。
函数首先初始化两个变量 res 和 sum,分别用于存储结果和当前子数组的和。然后,函数创建一个无序映射 umap,用于存储每个前缀和及其出现的次数。umap 的初始值为 {0, 1}
,表示前缀和为 0 的子数组出现了 1 次。
接着,函数遍历数组 nums
中的每个元素 n,将其加到 sum 上,然后计算当前子数组和减去 k 的结果 cur。如果 cur 在 umap
中存在,则表示存在一个前缀和为 cur 的子数组,其和为 k,因此将 umap[cur]
加到 res 上。最后,函数将当前子数组的和 sum 及其出现的次数加到 umap 中。
最后,函数返回 res,即所有和为 k 的子数组的数量。
问题解析
1.为什么使用unordered_map
?
答: 相比于map, unordered_map底层使用哈希表实现,是真正意义上的“哈希表”。(map底层实现为红黑树)(unordered_map在查询和插入上都比map快,但是缺少自动排序功能)
2.mp存在的意义是什么?
答: 假设当前前缀和为sum,我们的目标是求解一个连续的和为k的子数组,假设子数组标号从i到j,即求nums[i] + ... +nums[j] = k
,转化为前缀和就是sum[j] - sum[i-1] = k
。使用哈希时,我们在从左往右遍历nums,很明显,preSum为遍历到当前num的前缀和,即sum[j]。那sum[i-1]
去哪里了?所以我们需要使用一个数据结构,以便在我们需要之前的preSum时能够快速找到。
3.mp是如何使用的?
答: 由于题目要求我们求解sum[j] - sum[i-1] = k
的区间个数,我们将sum[i-1]
的有关信息放入mp,而当前状态我们只有sum,即sum[j]。对问题进行变形,可以转换为满足求解sum[i-1] == sum[j] - k
的区间个数,即如果之前就存在前缀和为sum[i-1]
的区间n个,那么满足sum - k的区间也会是n个。所以我们实际在mp中存放的内容就是前缀和为sum[i-1]的区间的个数。
为什么初始化mp[0] = 1
?
答: 刚开始还没有遍历nums,可以得到前缀和为0的区间有1个(区间内不包含任何元素)。
为什么要给mp[sum]++
?
答: 因为我们mp中记录的是前缀和为sum
的区间个数!!!比如数据[1, 2, 3, -3, 3]
中,前缀和sum为3的有[1, 2]和[1, 2, 3, -3]
两个,因此如果求解k=6的子数组,遍历到最后一个num时,根据这两个区间可以得到答案[3, -3, 3]和[3
](最后一个3)(当然答案还有[1, 2, 3]
,这个在前面就已经处理了)。
leetcode 1248. 统计「优美子数组]
给你一个整数数组 nums
和一个整数 k。如果某个连续子数组中恰好有 k 个奇数数字,我们就认为这个子数组是「优美子数组」。
请返回这个数组中 「优美子数组」 的数目。
示例 1:
输入:nums = [1,1,2,1,1], k = 3
输出:2
解释:包含 3 个奇数的子数组是 [1,1,2,1] 和 [1,2,1,1] 。
示例 2:
输入:nums = [2,4,6], k = 1
输出:0
解释:数列中不包含任何奇数,所以不存在优美子数组。
示例 3:
输入:nums = [2,2,2,1,2,2,1,2,2,2], k = 2
输出:16
AC代码
class Solution {
public:
int numberOfSubarrays(vector<int>& nums, int k) {
unordered_map<int,int>umap={{0,1}};
int sum=0,ans=0;
for(auto& a:nums){
sum+=(a&1);
int cur=sum-k;
if(umap.count(cur)){
ans+=umap[cur];
}
umap[sum]++;
}
return ans;
}
};
974.可被 K 整除的子数组
给定一个整数数组 nums 和一个整数 k ,返回其中元素之和可被 k 整除的(连续、非空) 子数组 的数目。子数组 是数组的 连续 部分。
示例 1:
输入:nums = [4,5,0,-2,-3,1], k = 5
输出:7
解释:
有 7 个子数组满足其元素之和可被 k = 5 整除:
[4, 5, 0, -2, -3, 1], [5], [5, 0], [5, 0, -2, -3], [0], [0, -2, -3], [-2, -3]
示例 2:
输入: nums = [5], k = 9
输出: 0
提示:
1 <= nums.length <= 3 * 104
-104 <= nums[i] <= 104
2 <= k <= 104
974题答案:
class Solution {
public:
int subarraysDivByK(vector<int>& nums, int k) {
unordered_map<int,int>umap={{0,1}};
int ans=0,sum=0;
for(auto& a:nums){
sum+=a;
int cur=(sum%k+k)%k;
if(umap.count(cur)){
ans+=umap[cur];
}
umap[cur]++;
}
return ans;
}
};
leetcode 523.连续的子数组和
给你一个整数数组 nums 和一个整数 k。编写一个函数来判断该数组是否含有同时满足下述条件的连续子数组:
子数组大小 至少为 2 ,且子数组元素总和为 k 的倍数。
如果存在返回 true ,否则返回 false
如果存在一个整数 n ,令整数 x 符合x = n * k
,则称 x 是 k 的一个倍数。0 始终视为 k 的一个倍数。
示例 1:
输入:nums = [23,2,4,6,7], k = 6
输出:true
解释:[2,4] 是一个大小为 2 的子数组,并且和为 6 。
示例 2:
输入:nums = [23,2,6,4,7], k = 6
输出:true
解释:[23, 2, 6, 4, 7]
是大小为 5 的子数组,并且和为 42 。
42 是 6 的倍数,因为 42 = 7 * 6
且 7 是一个整数。
示例 3:
输入:nums = [23,2,6,4,7], k = 13
输出:false
提示:
1 <= nums.length <= 105
0 <= nums[i] <= 109
0 <= sum(nums[i]) <= 231 - 1
1 <= k <= 231 - 1
523题答案:
class Solution {
public:
bool checkSubarraySum(vector<int>& nums, int k) {
unordered_map<int,int>umap={{0,-1}};
int ans=0,sum=0;
for(int i=0;i<nums.size();i++){
sum+=nums[i];
int cur=sum%k;
if(umap.count(cur)){
if(i-umap[cur]>=2)return true;
continue;
}
umap[cur]=i;
}
return false;
}
}
1512. 好数对的数目
给你一个整数数组 nums 。
如果一组数字 (i,j)
满足 nums[i] == nums[j] 且 i < j
,就可以认为这是一组好数对 。
返回好数对的数目。
示例 1:
输入:nums = [1,2,3,1,1,3]
输出:4
解释:有 4 组好数对,分别是 (0,3), (0,4), (3,4), (2,5)
,下标从 0 开始
示例 2:
输入:nums = [1,1,1,1]
输出:6
解释:数组中的每组数字都是好数对
示例 3:
输入:nums = [1,2,3]
输出:0
提示:
1 <= nums.length <= 100
1 <= nums[i] <= 100
1512题目答案
class Solution {
public:
int numIdenticalPairs(vector<int>& nums) {
std::unordered_map<int, int> countMap;
int pairsCount = 0;
for (int i = 0; i < nums.size(); ++i) {
// 检查当前元素在哈希表中的计数,如果有则表示存在相同的值
if (countMap.count(nums[i])) {
pairsCount += countMap[nums[i]];
}
// 更新当前元素在哈希表中的计数
countMap[nums[i]]++;
}
return pairsCount;
}
};