力扣第 411 场周赛题解

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;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值