这篇博客给大家介绍一下前缀和处理数组问题(配合哈希表)
560. 和为k的数组
给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。
我现在是发现了题目越简单 题好像就越难呀
O(n^3)的暴力(超时)
看到这个题开始没有其他头绪 然后就想着暴力看看能卡多少用例
int subarraySum(vector<int>& nums, int k) {
int ans = 0;
for (int i = 0; i < nums.size(); i++) {
for (int j = i + 1; j <= nums.size(); j++) {
int cur = 0;
for (int k = i; k < j; k++) {
cur += nums[k];
}
if (cur == k) {
ans++;
}
}
}
return ans;
}
然后我心想 还行 如果能想办法优化的话 可能就过了
O(n^2)的暴力(超时)
优化思路
int subarraySum(vector<int>& nums, int k) {
int ans = 0;
for (int i = 0; i < nums.size(); i++) {
int cur = 0;
for (int j = i; j >= 0; j--) {
cur += nums[j];
if (cur == k) {
ans++;
}
}
}
return ans;
}
还是卡了三个
利用前缀和
思路
我们仔细观察数组会发现 我们每次求得当前数组的和(一次遍历)将其存入哈希表中
然后遍历之前的位置 如果当前的值减去之前的值等于k就符合题意
int subarraySum(vector<int>& nums, int k) {
int cur = 0;
map<int, int> hash;
hash[0] = 1;//初始值算上
int ans = 0;
for (auto num : nums) {
cur+=num;
if (hash.find(cur - k) != hash.end()) {
//之前有符合条件的节点 加上其次数
ans += hash[cur - k];
}
hash[cur]++;
}
return ans;
}
代码是真的特别特别简单 主要是思想
1248.统计【优美子数组】
给你一个整数数组 nums 和一个整数 k。
如果某个 连续 子数组中恰好有 k 个奇数数字,我们就认为这个子数组是「优美子数组」。
请返回这个数组中「优美子数组」的数目。
这个题也是利用前缀和的思想 只不过这个题的和是当前位置之前所有符合位置的个数
如果你感觉上句话比较难理解我给大家举个例子
因为画图比较麻烦 而且用电脑上的画图软件 画出来也不是很美观
所以我尽量用语言描述清楚
假如题目给定数组是** [1,1,2,1,1] **
我们统计数目的数组就是** [0,1,2,2,3,4] **
为什么是这样呢 我们可以看到统计数目的数组的长度要多于给定数组长度
其中的第一个元素0 就表示当前给定数组中没有奇数 后边就是奇数的个数
然后就用前缀和和哈希表来实现计数
代码
int numberOfSubarrays(vector<int>& nums, int k) {
int ans = 0;
int cur = 0;
vector<int> arr;//计数数组
for (int i = 0; i < nums.size(); i++) {
arr.push_back(cur);
if (nums[i] % 2 == 1) {
cur++;
}
}
arr.push_back(cur);
map<int, int> hash;
for (int i = 0; i < arr.size(); i++) {
if (hash.count(arr[i] - k)) {
ans += hash[arr[i] - k];
}
hash[arr[i]]++;
}
return ans;
}
525.连续数组
给定一个二进制数组, 找到含有相同数量的 0 和 1 的最长连续子数组(的长度)。
这个题题目很短 也特别容易看懂题目的意思
所以刚开始我想到了用滑动窗口来解决
但是 仔细想了一下 如果用滑动窗口来解决的话 边界值不容易设置
比如 给定数组为 [1,1,1,1,0,0,0,0,0,0]这样的数组 我们很难找到边界条件
如果不熟悉滑动窗口解法的朋友可以参考之前的一篇博文
地址
所以这个题我们还是使用前缀和
但是有一个小技巧 我们可以看到 如果不处理数组的话 累加的值就会一直往上增
所以我们将0设置为-1 这样的话 如果当前有一段数据符合条件 那么这两个值就会相等
代码
int findMaxLength(vector<int>& nums) {
int cur = 0;
int ans = 0;
map<int, int> hash;
hash[0]=-1;
for (int i = 0; i < nums.size(); i++) {
cur += nums[i] == 1 ? 1 : -1;
if (hash.count(cur)) {
ans = max(ans,i - hash[cur]);
}
else {
hash[cur] = i;
}
}
return ans;
}
523.连续的子数组和
给定一个包含 非负数 的数组和一个目标 整数 k,编写一个函数来判断该数组是否含有连续的子数组,其大小至少为 2,且总和为 k 的倍数,即总和为 n*k,其中 n 也是一个整数。
这个题和上边的题有所区别 但是本质不离前缀和
我们在统计符合条件的同时 需要判断符合条件的两个元素的距离 如果符合返回true 否则继续判断
代码
bool checkSubarraySum(vector<int>& nums, int k) {
int cur = 0;
map<int, int>hash;
hash[0] = -1;
int ans = 0;
for (int i = 0; i < nums.size(); i++) {
cur += nums[i];
int t = k == 0 ? cur : cur % k;
cout<<t<<" ";
if (hash.count(t)) {
if (i - hash[t] >= 2) {
return true;
}
}else{
hash[t]=i;
}
}
return false;
}
希望通过这篇博文能让大家对前缀和的思想有一个更清晰的认识