和为k的子数组(中等)
给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。和为0的只是和为k的特殊情况。和为k的子数组
参考代码
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
class Solution {
public:
/**
* 前缀和
* 时间复杂度:O(n^2)
* 空间复杂度:O(n)
*/
int subarraySum(vector<int>& nums, int k) {
if (!nums.size()) return 0;
int n = nums.size();
vector<int> pre(n + 1);
for (int i = 1; i <= n; i++) pre[i] = pre[i - 1] + nums[i - 1];
int res = 0;
for (int i = 0; i < n; i++) {
for (int j = i + 1; j <= n; j++) {
if (pre[j] - pre[i] == k) res++;
}
}
return res;
}
/**
* 前缀和 + 哈希表
* 时间复杂度:O(n)
* 空间复杂度:O(n)
* sum[j] - sum[i] = k --> sum[i] = sum[j]- k,即变为去找sum[i]出现的次数。
*/
int subarraySum(vector<int>& nums, int k) {
if (!nums.size()) return 0;
unordered_map<int, int> hash;
hash[0] = 1;
int pre = 0, res = 0;
for (auto n : nums) {
pre += n;
if (hash.count(pre - k)) res += hash[pre - k];
hash[pre]++;
}
return res;
}
};
最近接零的子数组和(绝对值最小的子数组)
给定一个整数数组,找到一个和最接近于零的子数组。返回满足要求的子数组的起始位置和结束位置。给出[-3, 1, 1, -3, 5],返回[0, 2],[1, 3], [1, 1], [2, 2] 或者[0, 4]
参考代码
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
class Solution {
public:
typedef pair<int, int> PII;
/**
* 前缀和
* 时间复杂度:O(n^2)
* 空间复杂度:O(n)
*/
vector<int> subarraySumClosest(vector<int> nums) {
int n = nums.size();
vector<int> pre(n + 1);
for (int i = 1; i <= n; i++) pre[i] = pre[i - 1] + nums[i - 1];
unordered_map<int, PII> hash;
int res = INT_MAX;
for (int i = 1; i <= n; i++) {
for (int j = i; j <= n; j++) {
if (abs(pre[j] - pre[i - 1]) < res) {
res = abs(pre[j] - pre[i - 1]);
if (!hash.count(res)) hash[res] = {i, j};
}
}
}
return {hash[res].first - 1, hash[res].second - 1};
}
/**
* 前缀和+排序,则两个前缀和最近接的的话,其之间的子数组和越接近与0,所以对前缀和排序
* 时间复杂度:O(nlogn)
* 空间复杂度:O(n)
*/
vector<int> subarraySumClosest2(vector<int> nums) {
int n = nums.size();
int t = 0;
vector<PII> sum;
sum.push_back({0, 0});
for (int i = 0; i < n; i++) {
t += nums[i];
sum.push_back({t, i + 1}); // 坐标是从1开始的
}
sort(sum.begin(), sum.end()); // 排序
int a = -1, b = -1, diff = INT_MAX;
for (int i = 1; i < sum.size(); i++) {
if (abs(sum[i].first - sum[i - 1].first) < diff) {
diff = abs(sum[i].first - sum[i - 1].first);
// 注意这儿 pre[j] - pre[i] 计算的是[i+1,j]区间的和
a = min(sum[i].second, sum[i - 1].second); // 这儿应该是 a - 1 + 1
b = max(sum[i].second, sum[i - 1].second) - 1; // 这儿需要减一
}
}
return {a, b};
}
};
数组分为差值最小的两部分II
将一个数组按照中间某一位置分成两部分,使得两部分子数组的和的差值的绝对值最小。
参考代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
/**
* 前缀和
* 要使abs(前缀和 -(总和-前缀和)) = abs(2 * 前缀和 -总和)最小
* 时间复杂度:O(n)
*/
int absArray(vector<int>& nums) {
int n = nums.size();
vector<int> pre(n + 1);
for (int i = 1; i <= n; i++) pre[i] = pre[i - 1] + nums[i - 1];
int res = INT_MAX;
for (int i = 1; i <= n; i++) {
res = min(res, abs(pre[i] * 2 - pre[n]));
}
return res;
}
};
除自身以外数组的乘积(乘积数组)
给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。除自身以外数组的乘积
参考代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public:
/**
* 时间复杂度:O(n)
* 空间复杂度:O(1)
*/
vector<int> productExceptSelf(vector<int>& nums) {
int n = nums.size();
vector<int> res(n, 1);
int t = 1;
// 前面部分的累积
for (int i = 0; i < n; i++) {
res[i] *= t;
t *= nums[i];
}
t = 1;
// 后面部分的累积
for (int i = n - 1; i >= 0; i--) {
res[i] *= t;
t *= nums[i];
}
return res;
}
};
和可被k整除的子数组(中等)
给定一个整数数组 A,返回其中元素之和可被 K 整除的(连续、非空)子数组的数目。和可被K整除的子数组
参考代码
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
class Solution {
public:
/**
* 前缀和
* 时间复杂度:O(n^2)
* 空间复杂度:O(n)
*/
int subarraysDivByK(vector<int>& A, int K) {
int n = A.size();
vector<int> pre(n + 1);
for (int i = 1; i <= n; i++) pre[i] = pre[i - 1] + A[i - 1];
int res = 0;
for (int i = 0; i < n; i++) {
for (int j = i + 1; j <= n; j++) {
if ((pre[j] - pre[i]) % K == 0) res++;
}
}
return res;
}
/**
* 前缀和 + 哈希 + 同余定理
* 同余定理:如果整数a和整数满足a-b能够被m整除,即(a-b)%m==0,则a对m取余与b对m取余相等,即a%m==b%m。
* 如果两个数的差能被K整除,就说明这两个数 mod K得到的结果相同,
* 只要找有多少对 mod k 相同的数就可以组合一下得到结果。
* 时间复杂度:O(n)
* 空间复杂度:O(k)
*/
int subarraysDivByK(vector<int>& A, int K) {
int n = A.size();
unordered_map<int, int> hash;
hash[0] = 1; // 元素单独可以被k整除的情况
int pre = 0;
for (int i = 0; i < n; i++) {
pre += A[i]; // 前缀和
// 由于数组中有可能出现负数,我们需要将其加 K 从而使其 %K 之后的值为正数。
pre = (pre % K + K) % K;
hash[pre]++;
}
int res = 0;
// 找有多少对 mod k 相同的数就可以组合一下得到结果。
for (auto item : hash) {
res += item.second * (item.second - 1) / 2;
}
return res;
}
};
连续的子数组和(中等)
给定一个包含 非负数 的数组和一个目标 整数 k,编写一个函数来判断该数组是否含有连续的子数组,其大小至少为 2,且总和为 k 的倍数,即总和为 n*k,其中 n 也是一个整数。连续的子数组和
参考代码
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
class Solution {
public:
/**
* 前缀和 + 哈希表 + 同余定理
* 同余定理:如果整数a和整数满足a-b能够被m整除,即(a-b)%m==0,则a对m取余与b对m取余相等,即a%m==b%m。
* 如果两个数的差能被K整除,就说明这两个数 mod K得到的结果相同,
* 时间复杂度:O(n)
* 空间复杂度:O(n)
*/
bool checkSubarraySum(vector<int>& nums, int k) {
int n = nums.size();
int pre = 0;
unordered_map<int, int> hash;
hash[0] = -1;
for (int i = 0; i < n; i++) {
pre += nums[i];
if (!k) pre %= k;
if (hash.count(pre)) {
if (i - hash[pre] > 1)
return true;
} else hash[pre] = i;
}
return false;
}
/**
* 前缀和
* 时间复杂度:O(n^2)
* 空间复杂度:O(n)
*/
bool checkSubarraySum(vector<int>& nums, int k) {
int n = nums.size();
vector<int> pre(n + 1);
for (int i = 1; i <= n; i++) pre[i] = pre[i - 1] + nums[i - 1]; // 前缀和
for (int i = 0; i < n; i++) {
for (int j = i + 1; j <= n; j++) {
int sum = pre[j] - pre[i];
if (j - i > 1){
if (sum == k || (k && sum % k == 0)) return true;
}
}
}
return false;
}
// 暴力的话三层for循环,时间复杂度:O(n^3)
};
区域与检索(简单)
给定一个整数数组 nums,求出数组从索引 i 到 j (i ≤ j) 范围内元素的总和,包含 i, j 两点。区域和检索 - 数组不可变
参考代码
1
2
3
4
5
6
7
8
9
10
11
vector<int> sum;
NumArray(vector<int>& nums) {
sum = vector<int>(nums.size() + 1);
for (int i = 1; i <= nums.size(); i++) {
sum[i] = sum[i - 1] + nums[i - 1]; // 前缀和
}
}
int sumRange(int i, int j) {
return sum[j + 1] - sum[i];
}
矩阵区域和(中等)
给你一个 m * n 的矩阵 mat 和一个整数 K ,请你返回一个矩阵 answer ,其中每个 answer[i][j]
是所有满足下述条件的元素 mat[r][c]
的和: i - K <= r <= i + K, j - K <= c <= j + K 。矩阵区域和
思路:二维前缀和,参考题解
参考代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
vector<vector<int>> matrixBlockSum(vector<vector<int>>& mat, int K) {
int m = mat.size(), n = mat[0].size();
vector<vector<int>> sum(m + 1, vector<int>(n + 1)); // 计算前缀和
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
sum[i][j] = mat[i - 1][j - 1] + sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1];
}
}
vector<vector<int>> res(m, vector<int>(n));
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
// 计算满足条件矩形的左上和右下坐标
int x0 = max(i - K - 1, 0);
int x1 = min(i + K, m);
int y0 = max(j - K - 1, 0);
int y1 = min(j + K, n);
res[i - 1][j - 1] = sum[x1][y1] - sum[x0][y1] - sum[x1][y0] + sum[x0][y0];
}
}
return res;
}