把前面的數字都加起來稱為prefix sum。可以用來解區段和的問題。
最簡單的應用就是用\(O(1)\)算出某個subarray的和。
vector<int> nums;
// 使用STL來計算prefix sum
partial_sum(begin(nums), end(nums), begin(nums));
// 或是使用for loop
for(int i = 1; i < nums.size(); ++i)
nums[i] += nums[i - 1];
計算出prefix sum之後,就可以用start和end來求subarray sum。
// 求出 index = 2 ~ 5之間的subarray sum
cout << nums[5] - nums[1] << endl;
通常題目會要求符合某個條件的subarray sum。例如:求出subarray sum = K 的subarray個數。
使用unordered_set/map來記錄prefix sum的大小和個數,就可以用\(O(1)\)來查詢之前的pfx數值。 參考 560. Subarray Sum Equals K(Medium)
因為會往前尋找prefix sum,所以必須把0提前加入map或是set中。
因為0的意思是從第0個到目前的subarray sum。
unordered_map<int, int> m{{0, 1}};
unordered_set<int> s{{0}};
Time Complexity | Note | |
---|---|---|
initial | \(O(N)\) | 必須走訪全部的element來建立prefix sum array |
getSum | \(O(1)\) | 只需要計算pfs[end] - pfs[start - 1] |
update | \(O(N)\) | 如果一個數值變了必須update之後所有的pfs |
如果使用 BIT(Binary index tree) 就可以達到getSum和update都是\(O(logN)\)
求出綠色/藍色或是紅色框出來的sum。
計算2D prefix array,其中pfs[y][x]為起點是(0, 0)到(y, x)的sum。
vector<vector<int>> nums;
int m = nums.size();
int n = nums[0].size();
// 為了配合計算方便,使用大一點的vector
vector<vector<int>> pfs(m + 1, vector<int>(n + 1));
for(int y = 1; y <= m; ++y) {
for(int x = 1; x <= n; ++x) {
pfs[y][x] = pfs[y - 1][x] + pfs[y][x - 1] + matrix[y - 1][x - 1] - pfs[y - 1][x - 1];
}
}
計算某個範圍內的sum
int sumRegion(int row1, int col1, int row2, int col2) {
return pfs[row2 + 1][col2 + 1] -
pfs[row2 + 1][col1] -
pfs[row1][col2 + 1] +
pfs[row1][col1];
}
vector<vector<int>> nums;
for(auto& row : nums)
partial_sum(begin(row), end(row), begin(row));
int m = nums.size();
int n = nums[0].siez();
for(int left = 0; left < n; ++left) {
for(int right = left; right < n; ++right) {
}
}
nums[y][right] - (left - 1 >= 0 ? nums[y][left - 1] : 0);
int pfs = 0;
for(int y = 0; y < m; ++y) {
pfs += nums[y][right] -
(left - 1 >= 0 ? nums[y][left - 1] : 0);
}
給你一個vector<int> nums,求出subarray的和是k的個數。
- 一開始我是使用dp的方法,dp[i][j],其中i代表開始,j代表結束,dp[i][j] = dp[i][j - 1] + nums[j]。但是這個方法會timeout。因為為O(N^2)。
- 使用prefix sum,解法如下,也是會timeout。因為還是O(N^2)的演算法。
int subarraySum(vector<int>& nums, int k) {
int n = nums.size(), rtn{0};
vector<int> sum(n + 1, 0);
sum[1] = nums[0];
for(int i = 2; i <= n; ++i)
sum[i] = nums[i - 1] + sum[i - 1];
for(int i = 1; i <= n; ++i) {
for(int j = i; j <= n; ++j) {
if(sum[j] - sum[i - 1] == k) rtn++;
}
}
return rtn;
}
- 如下圖,當index走到i的時候,把這邊當終點,往前回推n個如果和是k,那這個subarray就是一個解。n前面的和即為sum - k。用一個hashmap紀錄prefix sum的個數,就可以達到O(N)。
int subarraySum(vector<int>& nums, int k) {
int n = nums.size(), rtn{0}, sum{0};
unordered_map<int, int> m{{0, 1}}; //self
for(auto& n : nums) {
sum += n;
rtn += m[sum - k];
m[sum]++;
}
return rtn;
}
- 題目是對sorted array做prefix sum,然後要找出排序後從left到right的總和。
- 因為是sorted array所以出現的subarray sum從小到大的順序可以預測。
// 這題的題目是對"sorted array" 做處理。 因為是sorted array[1, 2, 3, 4]
// 從1開始的subarray : [1], [1, 2], [1, 2, 3], [1, 2, 3, 4] ==> subarray sum = [1, 3, 6, 10]
// 從2開始的subarrry : [2], [2, 3], [2, 3, 4] ==> subarray sum = [2, 5, 9]
// 從3開始的subarray : [3], [3, 4] ==> subarray sum = [3, 7]
// 從4開始的subarray : [4] ==> subarray sum = [4]
int rangeSum(vector<int>& nums, int n, int left, int right) {
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
for(int i = 0; i < n; ++i)
pq.push({nums[i], i + 1}); // 每一個數字開頭的subarray,長度只有1
int ans = 0, m = 1e9 + 7;
for(int i = 1; i <= right; ++i) {
auto p = pq.top(); pq.pop(); // 先取出最小的
if(i >= left)
ans = (ans + p.first) % m;
if(p.second < n) { // 加上下一個數字,例如 : [2, 3] --> [2, 3, 4], 長度 + 1
p.first += nums[p.second++];
pq.push(p);
}
}
return ans;
}
有的題目必須將array轉換為正負值,這樣比較方便比較兩種不同性質的element。
題目是說數值大於8的為tiring day, well-performing interval定義為某個interval中tiring day的日子大於non-tiring day的日子。
- 將大於8的數值定義為1, 小於等於8的數值為-1。
- 使用prefix sum
- 一開始我是使用以下的code,從最開始找,直到sum - 1。
map<int, int> m;
for(auto it = m.begin(); it->first <= sum - 1; ++it) {
ans = max(ans, i - it->second);
}
- 因為我們只儲存第一個出現的index,越接近0的數值越早出現。所以我們只要檢查sum - 1即可。參考這邊的解說。
int longestWPI(vector<int>& hours) {
int ans = 0, sum = 0;
unordered_map<int, int> m;//value, index
m[0] = -1;
for(int i = 0; i < hours.size(); ++i) {
sum += hours[i] > 8 ? 1 : -1;
if(sum > 0) ans = max(ans, i + 1); // 因為大於0就是全部的長度是最長的
else {
if(m.count(sum - 1)) ans = max(ans, i - m[sum - 1]);
if(!m.count(sum)) m[sum] = i;
}
}
return ans;
}
// 這種問題都是從0開始
// 6, 6, 6, 9, 6, 9, 9
// 0|x
// -1| x x
// -2| x x x
// -3| x x
// 最後一個9, sum = -1, 可以找到符合的有-2, -3
// 因為我們都只存最前面的-2,而且要到-3,必須先經過-2,
// 所以-2永遠會比-3還前面,所以不用檢查小於-2的數值。
// 也就是我們只儲存第一個數值,所以越接近0的數值一定越早出現。
給你一個binary array,找出1和0數目一樣的最長subarray。
class Solution {
//[ 0, 0, 1, 1, 1, 0]
//[-1, -2, -1, 0, 1, 0]
// ------
public:
int findMaxLength(vector<int>& nums) {
unordered_map<int, int> m; // value, index
m[0] = -1; // 因為也是prefix sum的概念,所以必須加上開頭
int balance{0}, ans{0};
for(int i = 0; i < nums.size(); ++i) {
balance += nums[i] ? 1 : -1;
if(m.count(balance)) ans = max(ans, i - m[balance]);
else m[balance] = i;
}
// time : O(N)
// space : O(N)
return ans;
}
};
一開始看到了minimum operrations,就使用了DP結果TLE。看了hints,因為只能從最左和最右刪除,所以最後只會剩下subarray,所以問題就可變成找出最長的subarray,則sz - subarray.size() 就會是minimal operations。
下面的解法是用prefix sum + hash table。
time complexity : \(O(N)\)
space complexity : \(O(N)\)
int minOperations(vector<int>& nums, int x) {
int sz = nums.size();
int total = accumulate(nums.begin(), nums.end(), 0);
if(x > total) return -1;
if(x == total) return sz;
int target = total - x;
unordered_map<int, int> m;
m[0] = -1;
int sum = 0, maxlen = 0;
for(int i = 0; i < sz; ++i) {
sum += nums[i];
if(sum >= target && m.count(sum - target))
maxlen = max(maxlen, i - m[sum - target]);
m[sum] = i;
}
return maxlen > 0 ? sz - maxlen : -1;
}
給你一個2D Array求出,subarray的和為target的個數。
- 一開始使用 2D array算prefix的做法,但是time complexity非常差。
int numSubmatrixSumTarget(vector<vector<int>>& matrix, int target) {
int m = matrix.size();
int n = matrix[0].size();
vector<vector<int>> dp(m + 1, vector<int>(n + 1));
int rtn = 0;
for(int y = 0; y < m; ++y) { // bottom
for(int x = 0; x < n; ++x) { // right
dp[y + 1][x + 1] = dp[y][x + 1] + dp[y + 1][x] - dp[y][x] + matrix[y][x];
for(int i = 0; i <= y; ++i) { // top
for(int j = 0; j <= x; ++j) { //left
if(dp[y + 1][x + 1] - dp[i][x + 1] - dp[y + 1][j] + dp[i][j] == target) rtn++;
}
}
}
}
// time : O(MN * MN)
// space : O((MN))
return rtn;
}
- 這題是560. Subarray Sum Equals K(Medium)變成2D版本。
- 所以問題可以變成怎麼把2D版本變成1D版本。
- 先對row做prefix sum
- 再掃描left和right找出,符合條件的個數。
int numSubmatrixSumTarget(vector<vector<int>>& matrix, int target) {
int m = matrix.size();
int n = matrix[0].size();
for(int i = 0; i < m; ++i)
partial_sum(matrix[i].begin(), matrix[i].end(), matrix[i].begin()); // O(MN)
unordered_map<int, int> counter; // prefix sum, count
int ans = 0;
for(int left = 0; left < n; ++left) {
for(int right = left; right < n; ++right) {
counter = {{0, 1}}; // clear map and add only one item
int pfs = 0;
for(int k = 0; k < m; ++k) {
pfs += matrix[k][right] - (left - 1 >= 0 ? matrix[k][left - 1] : 0);
ans += counter[pfs - target];
counter[pfs]++;
}
}
}
// time : O(MN + MNM) = O(MNM)
// psace : O(MN)
return ans;
}
int maxSumSubmatrix(vector<vector<int>>& matrix, int k) {
for(auto& row : matrix)
partial_sum(begin(row), end(row), begin(row));
int m = matrix.size();
int n = matrix[0].size();
int ans = INT_MIN;
for(int left = 0; left < n; ++left) {
for(int right = left; right < n; ++right) {
int pfs = 0;
set<int> s{0};
for(int y = 0; y < m; ++y) {
pfs += matrix[y][right] - (left - 1 >= 0 ? matrix[y][left - 1] : 0);
auto it = s.lower_bound(pfs - k);
if(it != s.end()) ans = max(ans, pfs - *it);
s.insert(pfs);
}
}
}
return ans;
}
給你一個vector<int>和k,找出subarray sum為k的倍數。且長度至少為2。
- 一開到subarray sum就想到使用prefix sum
- 如果只是求剛好等於k的話,那就是查詢遇到的prefix sum始有否k - cur_pfs。
- 但是這邊是要k的倍數。k的倍數意味著,0, k, 2k, 3k, …
假設從第i個到第j個的subarry sum為k的倍數。
pfs(j) - pfs(i - 1) = m * k
兩邊各取mod k,則 pfs(j) % k - pfs(i - 1) % k = 0
=> pfs(j) % k = pfs(i - 1) % k
把prefix sum用mod K存起來,只要遇到和目前prefix sum mod k 相同的值,即為我們要的。
- 因為長度最少要2以上,所以對相同的prefix sum來說,我們只需要儲存index最小的那一個,因為這樣可以保證長度是最長的。
bool checkSubarraySum(vector<int>& nums, int k) {
if(k == 1) return nums.size() >= 2;
// 因為只要知道最小index的prefix sum(4)
unordered_map<int, int> m{{0, -1}}; // prefix sum, minamum idx
int pfs{0};
for(int i = 0; i < nums.size(); ++i) {
pfs = (pfs + nums[i]) % k;
if(m.count(pfs) && i - m[pfs] >= 2) return true;
if(!m.count(pfs)) m[pfs] = i;
}
return false;
}
給你一個vector<int>和K,找出subarray的個數使得subarray sum為可以被K整除。這邊數值會有負數。
- 因為subarray sum所以使用prefix sum
- 統計prefix sum出現的個數,就可以計算符合條件的subarray個數。
- 因為要可以整除K,所以觀念和523. Continuous Subarray Sum一樣。
pfs(cur) - pfs(prev) = m * K
pfs(cur) % K = pfs(prev) % K
- 可是這邊有負數!! 參考負數mod
因為 -1 % 3 = -2
所以使用 (n%k + k) % k,因為n為正整數結果不變。
如果是負數,則 -1 % 3 = -1。 (-1 + 3) % 3 = 2。
(1 + (-1) % 3 + 3) % 3 = 0,才是我們要的結果。
int subarraysDivByK(vector<int>& nums, int k) {
unordered_map<int, int> m{{0, 1}}; //prefix sum % k, count
int pfs{0}, ans{0};
for(auto& n : nums) {
pfs = (pfs + n % k + k) % k;
// pfs(cur) - pfs(prev) = m * k
// pfs(cur) % k = pfs(prev) % k
// 因為n 有可能為negative, 所以必須用 n % k + k
ans += m[pfs];
m[pfs]++;
}
return ans;
}
給你一個vector<int>和K,求出subarray sum最少為K,返回最短的長度,如果沒有則返回-1。
- 看到subarray sum那就是使用 prefix sum
- 因為是最短的長度,所以和523. Continuous Subarray Sum相反,當prefix sum一樣的時候,只要存最大index就可以。這樣長度才會最短。
- 另外使用long避免prefix sum加到overflow。
- 因為是subarray sum最少為k,
pfs(cur) - pfs(prev) >= k, i < j
pfs(prev) <= pfs(cur) - k
必須嘗試所有比pfs(cur) - k還小的值。
- 所以使用upper_bound找出 > pfs(cur) - k的值,然後從前一個開始嘗試。
- 最後把現在的pfs插入到map中,因為插入的index一定是最大值,所以把比目前的pfs還大的都刪除掉。
- 因為保留了最大index的pfs所以(5)不用全部都試,只需要試前upper_bound的前一個即可。
int shortestSubarray(vector<int>& nums, int k) {
int sz = nums.size();
map<long, int> m{{0, -1}}; //prefix sum, maximum index
int minans = sz + 1;
long pfs{0};
for(int i = 0; i < nums.size(); ++i) {
pfs += nums[i];
auto it = m.upper_bound(pfs - k);
if(it != m.begin()) {
it = prev(it, 1);
minans = min(minans, i - it->second);
}
m[pfs] = i;
while(m.rbegin()->first != pfs)
m.erase(m.rbegin()->first);
}
return minans == sz + 1 ? -1 : minans;
}
leetcode
刷題