3258. 统计满足 K 约束的子字符串数量 I
给你一个 二进制 字符串 s
和一个整数 k
。
如果一个 二进制字符串 满足以下任一条件,则认为该字符串满足 k 约束:
- 字符串中
0
的数量最多为k
。 - 字符串中
1
的数量最多为k
。
返回一个整数,表示 s
的所有满足 k 约束 的
子字符串
的数量。
示例 1:
输入:s = "10101", k = 1
输出:12
解释:
s
的所有子字符串中,除了 "1010"
、"10101"
和 "0101"
外,其余子字符串都满足 k 约束。
示例 2:
输入:s = "1010101", k = 2
输出:25
解释:
s
的所有子字符串中,除了长度大于 5 的子字符串外,其余子字符串都满足 k 约束。
示例 3:
输入:s = "11111", k = 1
输出:15
解释:
s
的所有子字符串都满足 k 约束。
提示:
1 <= s.length <= 50
1 <= k <= s.length
s[i]
是'0'
或'1'
。
题目说满足任意条件即可成立,也就是说两个条件同时都不满足时结果不成立.看到题目数据范围,我们可以采用暴力枚举每一种情况,时间复杂度O(n^2),可以过.
class Solution {
public:
int countKConstraintSubstrings(string s, int k) {
int count = 0;
for (int i = 0; i < s.size(); i++) {
int zeroCount = 0;
int oneCount = 0;
for (int j = i; j < s.size(); j++) {
if (s[j] == '0') zeroCount++;
if (s[j] == '1') oneCount++;
if (zeroCount <= k || oneCount <= k) {
count++;
}
}
}
return count;
}
};
但是我们要想优化,可以考虑以下标0,1,2,3.....为右端点的子串个数统计出来,例如10101,以下标0为右端点的子串为1;以下标1为右端点的子串为0,10;以下标2为右端点的子串为101,01,1......以此类推,然后把这些所有子串个数都加起来就是答案,但是,例如1010101,k=2,枚举到下标为5,即101010,就不符合要求了,但可以把子串缩小,例如01010,就符合要求了,不难发现如果这个长度为5的子串符合要求,那么长度更短的子串也符合要求,所以比如说右端点为0时,左端点最远为第一个0的位置,左端点L=1,右端点R=5,那么从[L,R],[L+1,R].....到[R,R]都是符合的,一共R-L+1个数,把这些数的个数加到答案里面就可以了,同时,当右端点增大时,比如增大到末尾的1时,就不符合要求了,所以要将左端点增大,所以可以推出当右端点增大时,左端点要么不变,要么增大,符合滑动窗口的思想,时间复杂度O(n),n为字符串长度,L只会增加,不会减少,所以为O(n).
class Solution {
public:
int countKConstraintSubstrings(string s, int k) {
int cnt[2],l=0,ans=0;
for(int i=0;i<s.size();i++){
cnt[s[i]%2]++;
while(cnt[0]>k&&cnt[1]>k)
cnt[s[l++]%2]--;
ans+=i-l+1;
}
return ans;
}
};
3259. 超级饮料的最大强化能量
来自未来的体育科学家给你两个整数数组 energyDrinkA
和 energyDrinkB
,数组长度都等于 n
。这两个数组分别代表 A、B 两种不同能量饮料每小时所能提供的强化能量。
你需要每小时饮用一种能量饮料来 最大化 你的总强化能量。然而,如果从一种能量饮料切换到另一种,你需要等待一小时来梳理身体的能量体系(在那个小时里你将不会获得任何强化能量)。
返回在接下来的 n
小时内你能获得的 最大 总强化能量。
注意 你可以选择从饮用任意一种能量饮料开始。
示例 1:
输入:energyDrinkA = [1,3,1], energyDrinkB = [3,1,1]
输出:5
解释:
要想获得 5 点强化能量,需要选择只饮用能量饮料 A(或者只饮用 B)。
示例 2:
输入:energyDrinkA = [4,1,1], energyDrinkB = [1,1,3]
输出:7
解释:
- 第一个小时饮用能量饮料 A。
- 切换到能量饮料 B ,在第二个小时无法获得强化能量。
- 第三个小时饮用能量饮料 B ,并获得强化能量。
提示:
n == energyDrinkA.length == energyDrinkB.length
3 <= n <= 105
1 <= energyDrinkA[i], energyDrinkB[i] <= 105
例如,[1,3,1]和 [3,1,1],题目中说每小时只能饮用一种能量饮料,且相邻的下标不能连续选择不同数组中的数,所以如果选定一个数组,可以连着选,但是选不同数组,只能跳着选.同时,题目保证所有数为正数,所以最后一个数一定要选(比如第一个数组,只选3和既选3又选1,肯定后者更优) (因为题目给了两个数组a,b,从a数组选若干数,从b数组选若干数,相当于从两个数组中各选了一个子序列,所以对于子序列题目,可以考虑最后一个数是怎么选的),所以选了最后一个数之后,接下来分两种情况,要么选同一个数组中的倒数第二个数,要么跳着选另一个数组的倒数第三个数(不能隔两个数选下一个,因为结果不优),所以这道题本质就是选或不选的题,比如选完第一个数组的最后一个1之后,接下来选第一个数组中的3,转化为从[1,3],[3,1]的选择情况;接下来选第二个数组中的3,就转化为没有数字选了.所以需要两个变量,一个是用i表示从下标0到下标i的数,第二个是用j表示从第i个位置选的数是属于a数组还是b数组(j=0表示从a数组选,也就是选了ai;j=1表示从b数组选,也就是选了bi).用dfs(i,j)表示从下标[0,i]中选,且下标i选的数为a[i](j==0),或者b[i](j==1),所以就是dfs(i-1,j)和dfs(i-2,1-j),二者取最大值,定义数组c,将数组a和b放进c中(下标0就是a,1就是b),还要考虑如果i<0,结果就越界了,所以直接返回0,所以dfs(i,j)=max(dfs(i-1,j),dfs(i-2,1-j))+c[j][i].结果返回的就是max(dfs(n-1,0),dfs(n-1,1)).考虑到整个递归过程中有大量重复递归调用(递归入参相同),由于递归函数没有副作用,同样的入参无论计算多少次,算出来的结果都是一样的,因此可以用记忆化搜索来优化.(python直接加@cache).也可以把记忆化搜索翻译成递推,当i=0时,递归到-2,i的范围是从-2到n-1,有n+2个数,j就是0,1.把dfs(-2,j)翻译成f[0][j],dfs(-1,j)翻译成f[1][j],dfs(i,j)=max(dfs(i-1,j),dfs(i-2,1-j))+c[j][i]翻译成f[i+2][j]=max(f[i+1][j],f[i][1-j])+c[j][i].(如果把数组c中的i加上2后,当i=n-1时,就越界了,所以不能加2,还有就是仅仅是在f数组前面插入状态,没在c数组前插入状态,所以不改变c数组),答案为 max(f[n+1][0],f[n+1][1]).
class Solution {
public:
long long maxEnergyBoost(vector<int>& a, vector<int>& b) {
int n = a.size();
vector<array<long long,2>>f(n + 2);
for (int i = 0; i < n; i++) {
f[i+2][0]=max(f[i+1][0],f[i][1])+a[i];
f[i+2][1]=max(f[i+1][1],f[i][0])+b[i];
}
return max(f[n+1][0],f[n+1][1]);
}
};
3260. 找出最大的 N 位 K 回文数
给你两个 正整数 n
和 k
。
如果整数 x
满足以下全部条件,则该整数是一个 k 回文数:
x
是一个 回文数。x
可以被k
整除。
以字符串形式返回 最大的 n
位 k 回文数。
注意,该整数 不 含前导零。
示例 1:
输入: n = 3, k = 5
输出: "595"
解释:
595 是最大的 3 位 k 回文数。
示例 2:
输入: n = 1, k = 4
输出: "8"
解释:
1 位 k 回文数只有 4 和 8。
示例 3:
输入: n = 5, k = 6
输出: "89898"
提示:
1 <= n <= 105
1 <= k <= 9
在i位置填了数字a,也同时在n-1-i位置填了数字a,在生成答案之前,只需考虑回文数%k的结果,例如三位数,中间位置初始化为0,则有9种情况(题目说不含前导零):101,202,.....909,接下来考虑每一个数中间填0~9又有10种情况,仅仅是三位数就有这么多种情况,如何减少考虑的数字的数量?这里有一个数学公式:(a+b)%k=(a%k+b%k)%k------取模恒等式,就是不用把整个数字生成出来去看%k的结果,可以在生成数字过程中直接%k.用i表示当前填到数字的哪一位,用j表示填的数字%k的结果.一开始为(0,0),当i=m时表示所有数都填好了(m=n/2上取整,比如一个6位数,从右到左i依次取0,1...5,当i=2时,还没填好,当i=3时,所有数字都填好了,比如一个5位数,i=2时继续填,i=3时填好了),即从起点(0,0)--->终点(m,0),m=n/2上取整.期间状态为从(1,(9*10的n-1次方+9)%k).....到(1,(1*10的n-1次方+1)%k),又可以递归到(i+1,....)...可以将题目考虑为图论问题,就是经过一些点最后到达终点,路径可以表示出填哪些数字,就是dfs暴搜加上vis数组记录访问过的结点.
class Solution {
public:
bool dfs(int i,int j,int n,int k,int m,vector<int>& pow10,string& ans,vector<vector<int>>&vis){
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(i+1,j2,n,k,m,pow10,ans,vis)){
ans[i]=ans[n-1-i]='0'+d;
return true;
}
}
return false;
}
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<int>>vis(m+1,vector<int>(k));
dfs(0,0,n,k,m,pow10,ans,vis);
return ans;
}
};
3261. 统计满足 K 约束的子字符串数量 II
给你一个 二进制 字符串 s
和一个整数 k
。
另给你一个二维整数数组 queries
,其中 queries[i] = [li, ri]
。
如果一个 二进制字符串 满足以下任一条件,则认为该字符串满足 k 约束:
- 字符串中
0
的数量最多为k
。 - 字符串中
1
的数量最多为k
。
返回一个整数数组 answer
,其中 answer[i]
表示 s[li..ri]
中满足 k 约束的子字符串的数量。
示例 1:
输入:s = "0001111", k = 2, queries = [[0,6]]
输出:[26]
解释:
对于查询 [0, 6]
, s[0..6] = "0001111"
的所有子字符串中,除 s[0..5] = "000111"
和 s[0..6] = "0001111"
外,其余子字符串都满足 k 约束。
示例 2:
输入:s = "010101", k = 1, queries = [[0,5],[1,4],[2,3]]
输出:[15,9,3]
解释:
s
的所有子字符串中,长度大于 3 的子字符串都不满足 k 约束。
提示:
1 <= s.length <= 105
s[i]
是'0'
或'1'
1 <= k <= s.length
1 <= queries.length <= 105
queries[i] == [li, ri]
0 <= li <= ri < s.length
- 所有查询互不相同
此题和第一题不同在于本题是要求子串中有多少个合法的子串,用Left[i]表示以i为右端点的子串通过滑动窗口求出的Left的位置,例如,一子串范围为[L,R],如果这个范围里所有的Left[i]都在L的左边,那么这里面所有子串都符合要求,长度为m的子串有1+2+3+...+m=(m+1)*m/2个子串,如果某个子串中有指向外面的,也有指向里面的,如果右端点指向的Left[i]在询问的区间内,那么直接可以把r-l+1加到答案里面,不难发现,每个位置都对应一个r-l+1,可以理解为关于r-l+1的子数组的和,就可以用前缀和优化.所以该题就分为两种情况:找一个位置,在这个位置左边所有子串全部都是合法的,直接用等差数列之和,在这个位置右边的子串,用前缀和算出来,所以回答每次询问,就取决于能不能快速找到分界位置,因为滑动窗口的左端点是不断向右移动的,所以这个Left数组是一个有序数组,可以在这个有序数组中二分找到大于等于L第一个位置作为分界位置.
class Solution {
public:
vector<long long> countKConstraintSubstrings(string s, int k, vector<vector<int>>& queries) {
int n=s.length(),m=queries.size();
vector<int>left(n);
vector<long long>sum(n+1);
int cnt[2]{},l=0;
for (int i=0;i<n;i++){
cnt[s[i]%2]++;
while(cnt[0]>k&&cnt[1]>k){
cnt[s[l++]%2]--;
}
left[i]=l;
// 计算 i-left[i]+1 的前缀和
sum[i+1]=sum[i]+i-l+1;
}
vector<long long>ans(m);
for (int i=0;i<m;i++){
int l=queries[i][0],r=queries[i][1];
int j=lower_bound(left.begin()+l,left.begin()+r+1,l)-left.begin();
ans[i]=sum[r+1]-sum[j]+(long long)(j-l+1)*(j-l)/2;
}
return ans;
}
};