lcPrefixSum - 前缀和 1

1 前缀和

1
2
3
4
vector<int> prefSum {nums[0]};
for(int i = 1; i < nums.size(); ++i) {
prefSum[i] += (nums[i] + prefSum.back());
}

1546最大非重叠数组

1 题目

https://leetcode-cn.com/problems/maximum-number-of-non-overlapping-subarrays-with-sum-equals-target/submissions/

2 解题思路

最为关键的一句话:
每次找结束序号最小的和为target的子数组,然后从这个子数组的后面开始搜寻下一个子数组,经典贪心。

前缀和,普通为o(n^2),使用hash优化:
subSum[j:i],本来需要遍历所有的j找出为k的subarray,
但是换个思路: 其实就是找i之前,有多少个前缀和为 preSum[j] - k,
那么我们把前缀和用hash存一下,那不就能够很快找到了?

但是有一个问题,在下一次搜寻开始的时候,需要将上一次搜寻过程的hash清零,
否则上一次的hash的结果会影响当前子序和。
而且假设上一次搜寻到j了,那么这次从j+1开始搜寻,必须保证前缀和也是从j+1开始

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class Solution {
public:
int maxNonOverlapping(vector<int>& nums, int target) {
int ans = 0;
// vector<int> preSum(nums.size());

// preSum[0] = nums[0];
// for(int i = 1; i < nums.size(); ++i ){
// preSum[i] = preSum[i-1] + nums[i];
// }
// while(ed < nums.size()) {
// for(int st = lastFoundEd; st <= ed; ++st) {
// if(preSum[ed] - preSum[st] + nums[st] == target) {
// ++ans;
// lastFoundEd = ed+1;
// break;
// }
// }
// ++ed;
// }

int st = 0;
int ed = 0;
int lastFoundEd = ed;

// map<int, int> hash;


while(st < nums.size()) {
ed = st;
// hash.clear(); // clear history to avoid repeat count
unordered_set<int> hash = {0};
int curSumFromLastFoundEd = 0;
while(ed < nums.size()) {
curSumFromLastFoundEd += nums[ed];
if(hash.count(curSumFromLastFoundEd - target)) {
++ans;
st = ed; // new search start
break;
} else {
hash.insert(curSumFromLastFoundEd);
}
++ed;
}
++st;
}

return ans;
}
};

0560subArraySum 子数组和为k

1 题目

https://leetcode-cn.com/problems/subarray-sum-equals-k/

2 解题思路

前缀和,普通为o(n^2),使用hash优化:
subSum[j:i],本来需要遍历所有的j找出为k的subarray,
但是换个思路: 其实就是找i之前,有多少个前缀和为 preSum[j] - k,
那么我们把前缀和用hash存一下,那不就能够很快找到了?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
vector<int> prefSum(nums.size());
prefSum[0] = nums[0];
for(int i = 1; i < nums.size(); ++i) {
prefSum[i] += (nums[i] + prefSum[i-1]);
}

int st = 0;
int ed = 0;
int ans = 0;
int curSum = 0;
std::unordered_map<int, int> hash;
hash[0] = 1;
while(ed < nums.size()) {
if(hash.find(prefSum[ed] - k) != hash.end()) {
ans += hash[prefSum[ed] - k];
}
hash[prefSum[ed]]++;


// for(int st = ed; st >= 0; --st) {
// curSum = prefSum[ed] - prefSum[st] + nums[st];
// if(curSum == k){
// ++ans;
// }
// }
++ed;
}

return ans;
}
};

1171移除和为0的子链表

1 题目:

https://leetcode-cn.com/problems/remove-zero-sum-consecutive-nodes-from-linked-list/

2 解题思路:

个人暴力思路:

  • 1 每次移除一个和为0的子数组,返回一个新的链表
  • 2 重复上述过程直到没有和为0的子数组

前缀和思路参考:
采用前缀和判断和为0方法
我们可以考虑如果给的入参不是链表是数组的话,只需要求出前缀和,对于前缀和相同的项,那他们中间的部分即是可以消除掉的,比如以 [1, 2, 3, -3, 4] 为例,其前缀和数组为 [1, 3, 6, 3, 7] ,我们发现有两项均为 3,则 6 和 第二个 3 所对应的原数组中的数字是可以消掉的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class Solution {
public:
ListNode* newHead;

ListNode* removeZeroSumSublists(ListNode* head) {
newHead = head;
while(removeOneZeroSubList(newHead)){ }
return newHead;
}

bool removeOneZeroSubList(ListNode* newHead) {
vector<int> valVec;
ListNode* head = newHead;
ListNode* p = head;
while(p != nullptr) {
valVec.emplace_back(p->val);
p = p->next;
}
size_t len = valVec.size();
vector<vector<int>> sumMat(len, vector<int>(len));

// cal all the sub len
// sumMat[a, b] = sumMat[a, b-1] + a
ListNode* stPtr = head;
ListNode* lastStPtr = nullptr;
for(int st = 0; st < len; ++st) {
sumMat[st][st] = valVec[st];
if(sumMat[st][st] == 0) {
if(nullptr == lastStPtr) {
this->newHead = head->next;
return true;
}
lastStPtr->next = stPtr->next;
return true;
}
ListNode* edPtr = stPtr->next;
for(int ed = st + 1; ed < len; ++ed) {
sumMat[st][ed] = sumMat[st][ed-1] + valVec[ed];
if(sumMat[st][ed] == 0) {
if(nullptr == lastStPtr) {
this->newHead = edPtr->next;
return true;
}
lastStPtr->next = edPtr->next;
return true;
}
edPtr = edPtr->next;
}
lastStPtr = stPtr;
stPtr = stPtr->next;
}
return false;
}
};

1292最大方块长度

1 题目

https://leetcode-cn.com/problems/maximum-side-length-of-a-square-with-sum-less-than-or-equal-to-threshold/

2 解题思路

  • 1 建立二维前缀和:
    1
    2
    3
    4
    5
    6
      vector<vector<int>> preSum(m+1, vector<int>(n+1, 0));
    for(int i = 1; i < m+1; ++i) {
    for(int j = 1; j < n+1; ++j) {
    preSum[i][j] = preSum[i-1][j] + preSum[i][j-1] - preSum[i-1][j-1] + mat[i-1][j-1];
    }
    }
  • 2 遍历所有边长的正方形即可(每个正放形为起点加上边长),遍历长度的过程可以通过二分搜索加速

值得思考的点:
如何快速得知当前位置是不是被阻挡了?
使用unordered_set作为hash快速查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
class Solution {
public:
int maxSideLength(vector<vector<int>>& mat, int threshold) {
// mat.row >= m > i >= 0, mat.col >= n > j >= 0
// preSum[m][n] -> preSum[i][j] = preSum[m][n] - preSum[m][j] - preSum[i][n] + preSum[i][j];
int m = mat.size();
int n = mat[0].size();
vector<vector<int>> preSum(m+1, vector<int>(n+1, 0));
for(int i = 1; i < m+1; ++i) {
for(int j = 1; j < n+1; ++j) {
preSum[i][j] = preSum[i-1][j] + preSum[i][j-1] - preSum[i-1][j-1] + mat[i-1][j-1];
}
}

// traverse all square
int maxSquareLen = std::min(m, n);
// for(int len = maxSquareLen; len >= 1; --len) { // len could be binary search
// for(int i = 0; i <= m - len; ++i) {
// for(int j = 0; j <= n - len; ++j) {
// if(getRec(preSum, i+1, j+1, i + len, j + len) <= threshold) {
// return len;
// };
// }
// }
// }
int stLen = 1;
int edLen = maxSquareLen;
int ans = 0;
while(stLen <= edLen) {
int len = (stLen + edLen) / 2;
bool found = false;
for(int i = 0; i <= m - len; ++i) {
for(int j = 0; j <= n - len; ++j) {
if(getRec(preSum, i+1, j+1, i + len, j + len) <= threshold) {
found = true;
};
}
}

if(found) { // len too small
stLen = len+1;
ans = len;
} else { // len too big
edLen = len-1;
}

}

return ans;
}

int getRec(vector<vector<int>>& preSum, int i, int j, int m, int n) {
return preSum[m][n] - preSum[m][j-1] - preSum[i-1][n] + preSum[i-1][j-1];
}
};

1124 良好表现的最长时间段

1 题目

https://leetcode-cn.com/problems/longest-well-performing-interval/

2 解题思路

较为容易想到前缀和思路:
不好想的地方在于第1和3条,思路如下:

  • 1 对于这种良好费良好的判断,我们需要把数组转换成 -1, 1的数组
  • 2 对上述数组作前缀和
  • 3 那么对于每个到来的j,只需要找到最小的i,满足 preSum[j] - preSum[i] == 1即可
    • 3.1 如何理解最小的i,也就是说,对于同一个preSum值的下标,我们总是去最小的i即可
    • 3.2 对于 9 9 9 9 9这种全正常的思路,如何判断? 前缀和的值本身就可以判断,大于零的下标都可以是良好工作区间
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Solution {
public:
int longestWPI(vector<int>& hours) {
vector<int> preSum = {0};
for(int i = 0; i < hours.size(); ++i) {
preSum.emplace_back((hours[i] > 8 ? 1 : -1) + preSum.back());
}

int st = 0;
int ed = 0;
int res = INT_MIN;
// key: presum's value, value: presum's index
map<int, int> m;
m[0] = 0;
int lastPop = 0;
vector<int> mono = {0};
for(; ed < hours.size(); ++ed) {
if (preSum[ed + 1] > 0) {
res = std::max(res, ed + 1);
continue;
}
map<int, int>::iterator it = m.find(preSum[ed + 1] - 1);
if(it != m.end()) {
res = std::max(ed - it->second, res);
}
if (m.find(preSum[ed + 1]) == m.end()) {
m[preSum[ed + 1]] = ed;
}

}
return res < 0 ? 0 : res;

}
};