题目列表
一、统计满足K约束的子字符串数量I
这种要求满足区间内某种性质的题,一般都可以用滑动窗口来做。这题也是同理,我们的思路是用滑动窗口来维护以 r 为右端点的满足题目区间性质的最长子字符串,然后统计答案即可,代码如下
class Solution {
public:
int countKConstraintSubstrings(string s, int k) {
int n = s.size(), ans = 0;
int cnt[2]{};
for(int l = 0, r = 0; r < n; r++){
cnt[s[r]-'0']++;
while(cnt[0] > k && cnt[1] > k){
cnt[s[l]-'0']--;
l++;
}
ans += r - l + 1; // 统计以 r 为右端点的满足要求的子串个数
}
return ans;
}
};
二、超级饮料的最大强化能量
这题和打家劫舍很相似。本题要求如果切换饮料要么需要有一个小时等待时间,本质就是不能相邻时间饮用不同的饮料,我们可以定义f[i]表示第 i 小时喝饮料A的最大能量,g[i]表示第 i 小时喝饮料B的最大能量,对于 f[i] 来说,它可以选择不切换饮料,从f[i-1]转移来,也可以选择谢欢饮料,从g[i-2]转移来,故转移方程为 f[i] = max(f[i-1],g[i-2]) + a[i],同理,g[i] = max(g[i-1],f[i-2]) + b[i]。为了防止下标越界,我们需要初始化。代码如下
class Solution {
using LL = long long;
public:
long long maxEnergyBoost(vector<int>& a, vector<int>& b) {
int n = a.size();
// f[i] 表示第 i 小时喝a饮料的最大能量
// g[i] 表示第 i 小时喝b饮料的最大能量
// f[i] = max(f[i-1], g[i-2]) + a[i]
// g[i] = max(g[i-1], f[i-2]) + a[i]
vector<LL> f(n + 2), g(n + 2);
for(int i = 0; i < n; i++){
f[i+2] = max(f[i+1], g[i]) + a[i];
g[i+2] = max(g[i+1], f[i]) + b[i];
}
return max(f.back(), g.back());
}
};
三、找出最大的N位K回文数
一般的想法就是去构造,由于回文数是中心对称的,所以我们只要枚举一半的数位填什么,就能知道整个回文数是多少,其次,由于题目只要求能被 k 整除的最大的n位回文数,所以我们贪心的让每一位都从 9 到 0 的顺序进行枚举,我们构造出的第一个满足条件的回文数就是最大的,我们可以直接返回true表示找到了一个合法的回文数,为了判断构造的回文数是否符合条件,我们还需要一个参数来记录 % k 的结果。
这题的关键在于如何进行取模?因为位数n太大会导致数据越界,我们计算出整个数之后再进行取模是不可取的,那么该如何做呢?根据取模的性质,(a+b)%k = ((a%k) + (b%k))%k,我们可以提前预处理得到每个位上的数 % k 的结果。
比如说800 % k = (100 % k + ... + 100%k)%k = (8 * 100 % k) % k,我们可以先计算出 10^i % k的结果,再结合枚举的数计算整体 % k 的值,这样就不会越界了,代码如下
class Solution {
public:
string largestPalindrome(int n, int k) {
vector<int> pow10(n);
pow10[0] = 1;
for (int i = 1; i < n; i++) {
pow10[i] = pow10[i - 1] * 10 % k;
}
string ans(n, '0');
int m = (n + 1) / 2;
vector<vector<bool>> vis(m + 1, vector<bool>(k));
auto dfs = [&](auto&& dfs, int i, int j) -> bool {
if (i == m) {
return j == 0;
}
vis[i][j] = true;
for (int d = 9; d >= 0; d--) { // 贪心:从大到小枚举
int j2;
if (n % 2 && i == m - 1) { // 正中间
j2 = (j + d * pow10[i]) % k;
} else {
j2 = (j + d * (pow10[i] + pow10[n - 1 - i])) % k;
}
if (!vis[i + 1][j2] && dfs(dfs, i + 1, j2)) {
ans[i] = ans[n - 1 - i] = '0' + d;
return true;
}
}
return false;
};
dfs(dfs, 0, 0);
return ans;
}
};
四、统计满足K约束的子字符串数量II
和第一题相比,第四题有多次查询需要处理,如何处理?
我们可以通过第一题的思路去尝试发现一些规律来帮助我们解决问题,首先,我们能知道每一个右端点都对应着一个左端点left,使得它的之间的长度最长,其次这些左端点的位置还是单调增的,因为滑动窗口一直在往右移动。
下面结合一个示例来帮助我们找到规律
代码如下
class Solution {
using LL = long long;
public:
vector<long long> countKConstraintSubstrings(string s, int k, vector<vector<int>>& q) {
int n = s.size();
vector<int>left(n);
vector<LL> pre(n+1);
int cnt[2]{};
for(int l = 0, r = 0; r < n; r++){
cnt[s[r] - '0']++;
while(cnt[0] > k && cnt[1] > k){
cnt[s[l] - '0']--;
l++;
}
left[r] = l;
pre[r+1] = pre[r] + r - l + 1;
}
vector<LL> ans(q.size());
for(int i = 0; i < q.size(); i++){
int l = q[i][0], r = q[i][1];
int j = lower_bound(left.begin() + l, left.begin() + r + 1, l + 1) - left.begin(); // left[j] > l
ans[i] = (long long)(j - l + 1) * (j - l) / 2 + pre[r+1] - pre[j];
}
return ans;
}
};