赞
踩
题目 示例
输入:nums = [1,1,1], k = 2
输出:2
输入:nums = [1,2,3], k = 3
输出:2
思路:
利用前缀和sum记录当前的和,区间[l,r]的和为: s[r+1] - s[l]
nums 1 0 1 1 0 2 [k = 2]
sum 0 1 1 2 3 3 5
利用哈希表map来存储 [历史前缀和], 题目要找的是 [当前前缀和 - 历史前缀和 == k] 的次数
所以需要遍历当前前缀和,如果 [sum - k] 出现在map中 cnt 次, 则结果 + cnt次,有 cnt 个子数组满足题意。
例如 i = 4时 sum = 3, sum - k = 1, mp[1] = 2, 说明有2个子数组满足, 即 [0,1,1]、[1,1]
代码:
class Solution { public: int subarraySum(vector<int>& nums, int k) { int n = nums.size(), cnt = 0; int sum = 0; // map优化 unordered_map<int, int> mp; // for(int i = 0; i < n; i++) s[i+1] = s[i] + nums[i]; // 必须遍历过程中计算前缀和 mp[0] = 1; // 处理边界:前缀和为 0 出现了 1 次 for(int r = 0; r < n; r++){ sum += nums[r]; if(mp.count(sum - k) > 0){ cnt += mp[sum - k]; } mp[sum] ++; } return cnt; } };
题目 示例
输入: nums = [0,1,0]
输出: 2
说明: [0, 1] (或 [1, 0]) 是具有相同数量0和1的最长连续子数组。
思路
1 表示 +1
0 表示 -1
用前缀和来表示1和0的数量
同样, 用map来存储历史前缀和第一次出现时的[下标]
nums 0 1 0 0 1 1
sum 0 -1 0 -1 -2 -1 0
遍历前缀和,如果当前前缀和在历史前缀和中出现过,说明区间 [l,r] 具有相同的0和1,更新ans
否则,记录当前前缀和第一次出现时的下标pos
代码
class Solution { public: int findMaxLength(vector<int>& nums) { int ans = 0, n = nums.size(); unordered_map<int, int> mp; mp.insert({0,-1}); //初始 0 的 pos = -1 int sum = 0; for(int r = 0; r < n; r++){ // 一边遍历一边计算前缀和 sum += nums[r] == 1 ? 1 : -1; // core/ if(mp.count(sum) > 0){ ans = max(ans, r - mp[sum]); } else mp[sum] = r; } return ans; } };
题目 示例
输入: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]
思路
[l,r]区间和: s[r+1] - s[l]
(s[r+1] - s[l]) % k == 0
<==> s[r+1] % k - s[l] % k == 0
<==> s[r+1] % k == s[l] % k
所以区间和 mod k == 0 的个数 等价于 前缀和s[r+1] % k 等于 s[l] % K 的个数
例如:
nums 4 5 0 -2 -3 1 k = 5
sum 0 4 9 9 7 4 5
%k 0 1 1 1 2 1 0
mp[1] = 4, mp[0] =2
前缀和%k = 1的次数为4, %k = 0的次数为2
cnt = ∑ N * (N-1) / 2 = 6 + 1 = 7
代码
class Solution { public: int subarraysDivByK(vector<int>& nums, int k) { // num 4 5 0 -2 -3 1 // s 0 4 9 9 7 4 5 //%k 0 1 1 1 2 1 0 // [l, r] --> s[r+1] - s[l] // s[r+1] - s[l] % k == 0 ? // s[r+1] % k == s[l] % k ? // mp[1] = 4 mp[0]=2 // n*(n-1) /2 int n = nums.size(); long long cnt = 0; int s[n+1]; s[0] = 0; unordered_map<int, long long> mp; for(int i = 0; i < n; i++) s[i+1] = s[i] + nums[i]; for(int i = 0; i <= n; i++){ while(s[i] < 0) s[i] = (s[i] + k) % k; s[i] = (s[i] + k) % k; mp[s[i]] ++; } for(auto [k, v] : mp){ // cout << k << " " << v << endl; cnt += (v - 1) * v / 2; } return cnt; } };
题目 示例
输入:nums = [23,2,4,6,7], k = 6
输出:true
解释:[2,4] 是一个大小为 2 的子数组,并且和为 6 。
思路
思路同上一题974,
如果map中存在 %k 后相同的历史前缀和,说明 [l,r] 区间和为k的倍数,只需再判断区间长度是否>=2即可
代码
class Solution { public: bool checkSubarraySum(vector<int>& nums, int k) { // (s[r+1] - s[l]) % k == 0 // s[r+1] % k == s[l] % k && r + 1 - l >= 2 int n = nums.size(); int s[n+1]; s[0] = 0; for(int i = 0; i < n; i++) s[i+1] = s[i] + nums[i]; for(int i = 0; i <= n; i++) s[i] = s[i] % k; unordered_map<int, int> mp; for(int r = 0; r <= n; r ++){ if(mp.count(s[r]) > 0 && r - mp[s[r]] >= 2){ return true; } if(mp.count(s[r]) == 0) mp[s[r]] = r; // 记录pos } return false; } };
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。