来自 英雄哪里出来 的一个 免费 集训,每天 5 5 5 点打卡学习算法(我是为了卷吗,主要是想早起 ),希望能坚持下去。这里用来复盘每天都的打卡题目。
今日份知识点:前缀和
今天是起飞的一天,摸一天。
题目 | 难度 |
---|---|
1838. 最高频元素的频数 | ⭐️⭐️ |
1590. 使数组和能被 P 整除 | ⭐️⭐️⭐️⭐️ |
1589. 所有排列中的最大和 | ⭐️ |
1712. 将数组分成三个子数组的方案数 | ⭐️⭐️⭐️ |
(1)【枚举】首先排序,将相同的元素放到相邻的位置,方便统计个数,记录每一组相同元素的开始下标以及个数;遍历计数数组,从记录的开始下标前一个依次往前取可能的最大频数,保留最大的频数。
时间复杂度: O ( n 2 ) O(n^2) O(n2)
class Solution {
public:
int maxFrequency(vector<int>& nums, int k) {
unordered_map<int, int> cnt;
sort(nums.begin(), nums.end());
for (auto i = 0, j = i; i < nums.size() && j < nums.size(); ++ i) {
while (j < nums.size() && nums[i] == nums[j]) ++ j;
i = j - 1;
cnt[i] = j - i;
}
int ret = 1;
for (auto x: cnt) {
int i = x.first;
int d = k;
int ans = x.second;
for (int j = i - 1; j >= 0; -- j) {
if (abs(nums[j] - nums[i]) <= d) {
++ ans;
d -= abs(nums[j] - nums[i]);
}else {
break;
}
}
ret = max(ret, ans);
}
return ret;
}
};
(2)【前缀和+二分+滑动窗口】先对原数组进行从小到大排序,如果存在最优解 len,意味着至少存在一个大小为 len 的区间 [l, r],使得在操作次数不超过 k 次的前提下,区间 [l, r] 的任意值为 nums[i] 调整为 nums[r]。二分答案 len 作为窗口长度,利用前缀和我们可以在O(1)的时间内获得任意区间的和,判断区间是否合理的条件为 nums[r] * len <= sum[l, r] + k
。注意:不开 long long
见祖宗。
时间复杂度: O ( n log n ) O(n\log{n}) O(nlogn)
class Solution {
public:
vector<int> nums;
vector<long long> sum;
int n, k;
int maxFrequency(vector<int>& _nums, int _k) {
nums = _nums;
k = _k;
n = nums.size();
sort(nums.begin(), nums.end());
sum.resize(n + 1);
for (int i = 1; i <= n; ++ i) {
sum[i] = sum[i - 1] + (long long)nums[i - 1];
}
int l = 0, r = n;
while (l < r) {
int mid = (l + r + 1) >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
return r;
}
bool check(int len) {
for (int l = 0; l + len - 1 < n; l++) {
int r = l + len - 1;
long long cur = sum[r + 1] - sum[l];
long long t = (long long)nums[r] * len;
if (t - cur <= k) return true;
}
return false;
}
};
【前缀和+哈希】
原理:我们需要找到一个最小的区间 s u b sub sub,使得
(sum - sub) % p = 0 ⟹ sum %p = sub % p \text{(sum - sub) \% p = 0} \\ \implies \text{sum \%p = sub \% p} (sum - sub) % p = 0⟹sum %p = sub % p 时间复杂度: O ( n ) O(n) O(n)
class Solution {
public:
int minSubarray(vector<int> nums, int p) {
int n = nums.size();
unordered_map<int, int> mp;
vector<long long> sum(n + 1);
for (int i = 1; i <= n; i ++ ) {
sum[i] = sum[i - 1] + nums[i - 1];
}
long long mod = sum.back() % p;
if (mod == 0) return 0;
mp[0] = -1;
int res = n;
for (int i = 1; i <= n; i ++) {
long long submod = sum[i] % p;
long long tarmod = (submod - mod + p) % p;
if (mp.count(tarmod)) {
res = min(res, i - mp[tarmod] - 1);
if (res == 1 && n != 1)
return 1;
}
mp[submod] = i - 1;
}
//cout << res << endl;
return res == n ? -1 : res;
}
};
【 贪 心 + 差 分 + 前 缀 和 】 【贪心+差分+前缀和】 【贪心+差分+前缀和】时间复杂度: O ( n log n ) O(n\log{n}) O(nlogn)
原理:我们最后需要得到 m m m 次查询的区间和的最大值。假如我们统计每个数组每个下标被查询的次数(某次查询的区间 [ l , r ] [l, r] [l,r],我们认为 [ l , r ] [l,r] [l,r] 的位置都被查询了一次),统计完每个位置的查询次数后,我们令数组中的最大值位于被查询次数最多的位置,以此类推,即可得到查询结果的最大值。
步骤:
(1)统计查询次数时,我们可以使用 差分
数组 s s s 记录(对于查询区间 [ l , r ] [l,r] [l,r],我们执行操作 s[l] ++, s[r + 1] --;
),可以在 O ( 1 ) O(1) O(1) 的时间里记录次数,之后进行前缀和操作可以在 O ( n ) O(n) O(n) 时间内还原查询的次数。
(2)分别对 n u m s nums nums 和 s s s 逆序排序, O ( n log n ) O(n\log{n}) O(nlogn);
(3)将两个数组对应下标的元素相乘,最后即答案, O ( n ) O(n) O(n)。
class Solution {
public:
int maxSumRangeQuery(vector<int>& nums, vector<vector<int>>& requests) {
long long MOD = 1e9 + 7;
int n = nums.size();
vector<long long> s(n + 1, 0);
for (int i = 0; i < requests.size(); i++) {
s[requests[i][0]] ++;
s[requests[i][1] + 1]--;
}
for (int i = 1; i <= n; i ++) {
s[i] += s[i - 1];
}
sort(s.begin(), s.end() - 1, greater<long long>());
sort(nums.begin(), nums.end(), greater<int>());
long long ans = 0;
for (int i = 0; i < n; i ++) {
ans += (s[i] * nums[i]);
}
return ans % MOD;
}
};
【 前 缀 和 + 暴 力 】 【前缀和+暴力】 【前缀和+暴力】时间复杂度: O ( n 2 ) O(n^2) O(n2)
(1)对数组求前缀和数组 s u m sum sum;
(2)遍历中间数组的起点 [ 1 , n − 1 ] [1,n-1] [1,n−1]:
(3)找到一个最小的区间使得 中间的数组和大于等于第一个数组和
,记录这时的第二个下标 l l l;
(4)找到一个最大的区间使得 中间的数组和小于等于最后一个的数组和
,记录这时的下标 r r r;
(5)当前起点的可切分方案为 r − l + 1 r-l+1 r−l+1。
分 析 : \color{red}分析: 分析:最大的数据量为 1 0 5 \color{red}10^5 105,那么计算量为 1 0 10 \color{red}10^{10} 1010,那么肯定会 T L E \color{red}TLE TLE,我们需要 优 化 \color{green}优化 优化。
【 前 缀 和 + 二 分 查 找 】 【前缀和+二分查找】 【前缀和+二分查找】时间复杂度: O ( n log n ) O(n\log{n}) O(nlogn)
优化方向:将步骤(3)、(4)使用 二 分 二分 二分 优化,使时间复杂度从 O ( n ) O(n) O(n) 降为 O ( log n ) O(\log{n}) O(logn),那么总体的时间复杂度降为 O ( n log n ) O(n\log{n}) O(nlogn),计算次数为 1 0 8 \color{green}10^8 108。
class Solution {
public:
int waysToSplit(vector<int>& nums) {
int n = nums.size();
long long MOD = 1e9 + 7;
vector<long long> sum(n + 1);
for (int i = 1; i <= n; i ++) {
sum[i] = sum[i - 1] + nums[i - 1];
}
long long ans = 0;
for (int i = 2; i < n; i ++) {
long long pre = sum[i - 1];
int l = i, r = n - 1;
while (l < r) {
int mid = l + (r - l) / 2;
if (sum[mid] - sum[i - 1] >= pre) r = mid;
else l = mid + 1;
}
if (sum[r] - pre < pre || sum[n] - sum[r] < sum[r] - pre)
continue;
int ll = r, rr = n - 1;
while (ll < rr) {
int mid = ll + (rr - ll + 1) / 2;
if (sum[mid] - pre <= sum[n] - sum[mid]) ll = mid;
else rr = mid - 1;
}
ans += ll - r + 1;
}
return ans % MOD;
}
};