除了求最大子段和用的 O(n)动态规划,求其他满足“某种条件"的子段和可用”前缀和“解法。runningSum 对应“前缀和”数组的“后闭”模式。runningSum 可以认为是滑动窗口的特例。
和为 target 的子段数
- https://leetcode.com/problems/binary-subarrays-with-sum/
- https://leetcode.com/problems/subarray-sum-equals-k/
- https://leetcode.com/problems/maximum-size-subarray-sum-equals-k/
int numSubarraysWithSum(vector<int>& A, int target) {
unordered_map<int, int> presum = {{0,1}}; // sum=>count
int runningSum = 0;
int ans = 0;
for (int a : A) {
runningSum += a;
// 需S=runningSum-toFind
int toFind = runningSum - target;
if (presum.count(toFind)) ans += presum[toFind];
presum[runningSum]++;
}
return ans;
}
和为 target 的不重叠子段数
- https://leetcode.com/problems/maximum-number-of-non-overlapping-subarrays-with-sum-equals-target/
int maxNonOverlapping(vector<int>& nums, int target) {
const int N = nums.size();
// 判断重叠,使用sum=>idx、记录lastIdx
unordered_map<int, int> presum = {{0, -1}}; // sum=>idx
int runningSum = 0, lastIdx = -1, ans = 0;
for (int i = 0; i < N; i++) {
runningSum += nums[i];
// runningSum-toFind=target
int toFind = runningSum - target;
if (presum.count(toFind)) {
// 保证不重叠,presum相关是前开后闭区间
if (presum[toFind] >= lastIdx) {
ans++;
lastIdx = i;
}
}
// 多个相同值保留最右的,因为前面的已处理过
presum[runningSum] = i;
}
return ans;
}
最接近 target 的子段和
假设已把前面各累加和放到的 set 中,对当前 runningSum 要在 set 中找 toFind 使:runningSum-toFind 最接近 target,即找最接近 runningSum-target 的旧和值 toFind。设 x=runningSum-target,看备选 it=lower_bound(x)和 prev(it)哪个更接近 x。
java 代码可参考,jave 中 TreeSet 对应 c++的 std::set
落在范围[lower,upper]的子段和有多少
用 map 记录各累加和出现的次数。对当前 runningSum 要在 map 中查找 toFind 使 lower<=runningSum-toFind<=upper,runningSum-upper<=toFind<=runningSum-lower。
int countRangeSum(vector<int>& nums, int lower, int upper) {
map<long, int> cnt = {{0, 1}} // sum=>count
long runningSum = 0;
int ans = 0;
for (int num : nums) {
runningSum += num;
// 找数x使lower<= runningSum-x <=upper,runningSum-upper<= x <=runningSum-lower
auto it1 = cnt.lower_bound(runningSum - upper);
auto it2 = cnt.upper_bound(runningSum - lower);
for (auto it = it1; it != it2; it++) {
ans += it->second;
}
cnt[runningSum]++;
}
return ans;
}
可被 k 整除的子段和有多少
int subarraysDivByK(vector<int>& A, int K) {
unordered_map<int, int> cnt = {{0, 1}}; // sum=>count
int runningSum = 0;
int ans = 0;
for (int a : A) {
runningSum = (runningSum + a % K + K) % K; // a可能为负
// (runningSum-toFind)%K==0,(toFind==runningSum)%K
ans += cnt[runningSum];
cnt[runningSum]++;
}
return ans;
}
子段和等于 k 的倍数、且子段长>=2
bool checkSubarraySum(vector<int>& nums, int k) {
unordered_map<int, int> mp = {{0, -1}}; // sum=>idx
int runningSum = 0;
// 子段和是k的倍数,即子段和(runningSum-toFind)%k==0
// (toFind==runningSum)%K
// 为让子段尽量长,多个相同runningSum的只保留第一个
for (int i = 0; i < nums.size(); i++) {
runningSum += nums[i];
if (k) runningSum %= k;
if (!mp.count(runningSum)) {
mp[runningSum] = i;
} else if (i - mp[runningSum] >= 2) {
return true;
}
}
return false;
}
子段在重排和替换 k 个字符后,能够成回文串吗
vector<bool> canMakePaliQueries(string s, vector<vector<int>>& queries) {
// "前缀和"preOdds[i+1]对应s[0..i],各个位对应各字符的奇偶性
vector<int> preOdds = {0};
int runningOdds = 0;
for (char c : s) {
runningOdds ^= 1 << (c - 'a');
preOdds.push_back(runningOdds);
}
vector<bool> ans;
for (auto &query : queries) {
// 回文需要oddCnt/2<=k
int odds = preOdds[query[1] + 1] ^ preOdds[query[0]];
int oddCnt = bitset<26>(odds).count();
ans.push_back(oddCnt / 2 <= query[2]);
}
return ans;
}
两个不重叠子段的最大和
- https://leetcode.com/problems/maximum-sum-of-two-non-overlapping-subarrays/
int maxSumTwoNoOverlap(vector<int>& A, int l1, int l2) {
// 题目:两个不重叠子段,l1在l2前、或l2在l1前
const int N = A.size();
vector<int> presum(N + 1, 0);
for (int i = 0; i < N; i++) {
presum[i + 1] = presum[i] + A[i];
}
int l1max = 0, l2max = 0, ans = 0;
for (int i = l1 + l2; i <= N; i++) {
// l1在l2前
l1max = max(l1max, presum[i - l2] - presum[i - l1 - l2]);
ans = max(ans, l1max + (presum[i] - presum[i - l2]));
// l2在l1前
l2max = max(l2max, presum[i - l1] - presum[i - l1 - l2]);
ans = max(ans, l2max + (presum[i] - presum[i - l1]));
}
return ans;
}