题目链接:https://leetcode-cn.com/problems/subarray-sum-equals-k/
- 因为此题要求是连续,所以该题一般思路是枚举子数组的左右端点 i , j i,j i,j,然后用前缀和数组 p r e [ j ] − p r e [ i − 1 ] pre[j]-pre[i-1] pre[j]−pre[i−1]优化查询时间。但是此题数据量是2e4,仍然TLE。
- 但是,一段连续区间的子数组(子串)似乎还可以用双指针:右指针向右滑,知道满足和为k,但是可能这样的区间里满足和为k的子串已经不止一个了:-4,4,k。也就说如何滑动两个指针不漏掉解(统计出准确的解)很难办到。
- 思路:符合题意的子数组形式一般都是这样的: p r e [ 0... i − 1 ] p r e [ i . . . j ] , ( p r e [ i , j ] = = k ) pre[0...i-1] pre[i...j],(pre[i,j]==k) pre[0...i−1]pre[i...j],(pre[i,j]==k),因此我们只需要知道满足前缀和为 p r e [ j ] − k pre[j]-k pre[j]−k的前缀和有多少个即可。而且,并不需要构造前缀和数组,我们这里并不需要通过下标去查询某一段的区间和,只需要知道符合刚才分析的前缀和有多少个就行了。
- 算法:
- 依次遍历每个数,用一个变量 s u m sum sum代表前缀和,边遍历边加上当前数。
- 用哈希表去记录每个前缀和对应的个数,对于当前遍历到的前缀和 s u m sum sum,查询 s u m − k sum-k sum−k这个前缀和是否出现过,出现过就加上出现过的次数,这就是满足题意得子数组的个数。
- 将当前的前缀和加到哈希表里。
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
//2e4
unordered_map<int, int> rec;
rec[0] = 1;
int sum = 0, ans = 0;
for (const int & c : nums){
sum += c;
if (rec.count(sum - k)) ans += rec[sum - k];
rec[sum]++;
}
return ans;
}
};
题目连接:https://leetcode-cn.com/problems/maximum-number-of-non-overlapping-subarrays-with-sum-equals-target/
- 这题和上题差不多,只不过这里要求不重叠。
- 思路:依然是通过前缀和减去目标值可以快速得到之前出现过的前缀和(如果有的话),并且需要用(哈希)表记录下来快速查询,这样能快速得到满足和为目标值的子数组对应的信息,在这题里需要记录对应(相同)前缀和的最右下标,以此来防止重合。
- 算法:
- 状态定义: d p ( i ) dp(i) dp(i)表示前i个中和为 t a r g e t target target的数组的集合,属性为 c n t cnt cnt。
- 状态转移: d p ( i ) = m a x ( d p ( i − 1 ) , d p ( r e c [ s u m − t a r g e t ] ) + 1 ) dp(i)=max(dp(i-1),dp(rec[sum-target])+1) dp(i)=max(dp(i−1),dp(rec[sum−target])+1),因为我们用哈希表快速记录了 s u m − t a r g e t sum-target sum−target的前缀和,以及出现这个前缀和的对应下标,所以从这个下标j到i这一段刚好能凑成和为目标值的子数组,然后用 d p ( j ) + 1 , d p ( i − 1 ) dp(j)+1,dp(i-1) dp(j)+1,dp(i−1)来转移。并且,不需要担心子数组重叠的问题,因为我们这里取的是 m a x max max。
- 初始化: r e c [ 0 ] = 0 rec[0]=0 rec[0]=0 (前缀和为0的对应下标就是元素0,因为这里从1开始枚举,否则是-1)
- 注意事项:每次迭代,我们需要更新当前前缀和出现的下标,即使这个前缀和出现过。例如: r e c [ k ] = i rec[k]=i rec[k]=i更新成 r e c [ k ] = j ( j > i ) rec[k]=j(j>i) rec[k]=j(j>i),这样下次转移的时候,那个 d p ( j ) dp(j) dp(j)会更大,这里贪心选择。
class Solution {
private:
unordered_map<int, int> rec;
static const int N = 1e5 + 5;
int dp[N];
public:
int maxNonOverlapping(vector<int>& nums, int target) {
//1e5
//dp(i)表示前i个中和为target的数组的集合,属性为cnt
memset(dp, 0, sizeof(dp));
rec.clear();
int sum = 0, n = nums.size();
rec[0] = 0;
for (int i = 1; i <= n; ++i){
sum += nums[i - 1];
if (rec.count(sum - target))
dp[i] = max(dp[i - 1], dp[rec[sum - target]] + 1);
else dp[i] = dp[i - 1];
rec[sum] = i;
}
return dp[n];
}
};
总结:我们总是将求子数组和转化为前缀和减去子数组和这样的前缀和是否存在。