Given an array nums and an integer target.
Return the maximum number of non-empty non-overlapping subarrays such that the sum of values in each subarray is equal to target.
Example 1:
Input: nums = [1,1,1,1,1], target = 2
Output: 2
Explanation: There are 2 non-overlapping subarrays [1,1,1,1,1] with sum equals to target(2).
Example 2:
Input: nums = [-1,3,5,1,4,2,-9], target = 6
Output: 2
Explanation: There are 3 subarrays with sum equal to 6.
([5,1], [4,2], [3,5,1,4,2,-9]) but only the first 2 are non-overlapping.
Example 3:
Input: nums = [-2,6,6,3,5,4,1,2,8], target = 10
Output: 3
Example 4:
Input: nums = [0,0,0], target = 0
Output: 3
Constraints:
1 <= nums.length <= 105
-104<= nums[i] <= 104
0 <= target <= 106
题目是给定了一个数组,求不重叠的求和为target的子数组最多同时存在多少个。从数据规模来看,数组最长是10万,算法的复杂度一定是o(nlogn)或者更低。n2肯定是过不了的。
一般求解子数组和的题目首先考虑前向累计和(presum)。如果当前的presum是s,那我们只需要找此前有没有出现一个presum是s - t(t代表target,简写一下),那么就存在一段子数组以当前数字结尾并且满足和是t。此前presum是s-t出现过的位置可能有多个,取哪个呢?回答是取最后出现的那个。很容易看出来的一个结论就是长度越长,最后的结果有机会越大。如果前m个数字就能找出最多有k个满足条件的不重叠数组。那前m+1个数字肯定不会比前m个数字得出的结果差,至少满足前m个数的那个最优解在前m+1个数字里还是一个可行解。所以随着长度的增加,res的值只可能单调不减。
在当时做周赛的时候我想到是用hash map配合dp求解
dp[i]表示前i个数能求到的最优解,根据前面描述,dp[i]的值一定是单调不减
index[presum]表示上一个presum出现的位置
const int N = 1e5 + 10;
//dp[i]表示前i个数字能得到的最优解
int dp[N];
class Solution {
public:
int maxNonOverlapping(vector<int>& nums, int t) {
memset(dp, 0, sizeof dp);
unordered_map<int, int> index; //key是index, value是[0,key]这个范围能求出的最大值
int n = nums.size();
index[0] = -1;
int s = 0; //这个是presum
int res = 0;
for ( int i = 0; i < n; ++i ) {
s += nums[i];
if ( index.count(s - t) ) {
//这里写index[s-t]+1是因为dp[i]表示的是前i个数,和index有1个偏移
res = max(res, dp[index[s - t] + 1] + 1);
}
index[s] = i;
dp[i + 1] = res;
}
return res;
}
};
这段代码的思路是先找到s-t上一次出现的位置,用那个位置的最优解来计算当前的可能最优解。
看了discuss里面的高票答案,发现自己的周赛代码中有一些冗余,周赛代码中是通过presum找到index,然后根据index来找之前的最优解。其实没这个必要,index这一步完全可以省去,只需要记录下来当前累计和捆绑的最优解是多少就可以了。就算之前出现过当前的presum,那当前的也一定不会比之前的差(单调性结论),更何况每次查找s - t,都是查找最后出现的那个,因为最后出现的那个覆盖的范围最大。
class Solution {
public:
int maxNonOverlapping(vector<int>& nums, int t) {
unordered_map<int, int> sum; //key是presum, value是这个presum对应的最优解
int n = nums.size();
sum[0] = 0;
int presum = 0;
int res = 0;
for ( int i = 0; i < n; ++i ) {
presum += nums[i];
if ( sum.count(presum - t) ) {
res = max(res, sum[presum - t] + 1);
}
sum[presum] = res;
}
return res;
}
};
时间空间都是o(n)
这题本质上和2 sum差不多,利用res单调性可以使用贪心。