第 411 场周赛

100402. 统计满足 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'

思路:自己的做法(比较传统),就是遍历这个字符串的子字符串的个数,然后统计0和1的个数,满足条件再相加。

class Solution {
    public int countKConstraintSubstrings(String s, int k) {
        int count1=0;
        for(int i = 0; i < s.length(); i++){
            for (int j = i+1; j<=s.length(); j++){
                count1+=count(s.substring(i,j),k);
            }
        }
        return count1;
    }
    public int count(String s,int k){
        int result0=0,result1=0;
        for(int i=0;i<s.length();i++){
            if(s.charAt(i)=='1'){
                result1++;
            }
            if(s.charAt(i)=='0'){
                result0++;
            }
        }
        if(result0<=k||result1<=k){
            return 1;
        }else{
            return 0;
        }
    }
}

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 <= 10^5
  • 1 <= energyDrinkA[i], energyDrinkB[i] <= 10^5

自己的做法 就是简单的动态规划

class Solution {
    public long maxEnergyBoost(int[] energyDrinkA, int[] energyDrinkB) {
        int n = energyDrinkA.length;
        long[][] dp = new long[n][2]; // dp[i][0]表示在第i小时只喝A能获得的最大强化能量,dp[i] 
        [1]表示在第i小时只喝B能获得的最大强化能量
        //初始化
        dp[0][0] = energyDrinkA[0];
        dp[0][1] = energyDrinkB[0];

        for (int i = 1; i < n; i++) {
            //遍历条件,切换到别的能量这个小时不获得任何能量,喝自己能量的话就加上这个小时所获得的。
            dp[i][0] = Math.max(dp[i-1][0]+energyDrinkA[i], dp[i-1][1]);
            dp[i][1] = Math.max(dp[i-1][1]+energyDrinkB[i], dp[i-1][0]);
        }
        //两者取最大值
        return Math.max(dp[n-1][0], dp[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 <= 10^5
  • 1 <= k <= 9

思路:自己的方法很简单,就是暴力,结果结局不出所料,超时了。

class Solution {
    public String largestPalindrome(int n, int k) {
         // 找到n位数的最大值
        int maxNum = (int) Math.pow(10, n) - 1;

        // 从最大值开始,找出最大的k回文数
        for (int i = maxNum; i >= 0; i--) {
            String strNum = String.valueOf(i);
            if (isPalindrome(strNum) && i % k == 0) {
                return strNum;
            }
        }

        return "-1"; // 如果找不到满足条件的数,返回-1
    }
     // 判断一个字符串是否是回文数
    public static boolean isPalindrome(String str) {
        int len = str.length();
        for (int i = 0; i < len / 2; i++) {
            if (str.charAt(i) != str.charAt(len - i - 1)) {
                return false;
            }
        }
        return true;
    }

}

于是从网上看别人的解法,进一步更优的思路。

思路和算法
为了得到最大的 n 位 k 回文数,应遵循如下贪心策略:从最高位开始分别对每一位选取可能的最大值。根据回文数的性质,最高位取最大值等价于最低位取最大值,因此需要考虑最低位是否满足整除要求。

根据贪心策略,应有限考虑每一位填入数字 9。如果最高位和最低位填入数字 9 无法满足整除要求,则考虑最高位和最低位填入其他数字。

由于 k 的取值范围有限,因此可以根据 k 的值生成最大的 n 位 k 回文数。

当 k 的值等于 1、3 或 9 时,所有数位填入数字 9,没有任何约束。

当 k 的值等于 2 时,最低位是偶数才能被 k 整除,最大的一位偶数是 8,因此最高位和最低位填入数字 8,其余数位填入数字 9。

当 k 的值等于 5 时,最低位是 5 的倍数才能被 k 整除,能被 5 整除的最大一位数是 5,因此最高位和最低位填入数字 5,其余数位填入数字 9。

当 k 的值等于 4 时,最低两位是 4 的倍数时才能被 k 整除,符合要求的最高两位的最大值是 88。需要考虑 n<=4 和 n>4 的情况。

当 n<=4 时,所有数位填入数字 8。

当 n>4 时,最高两位和最低两位填入数字 8,其余数位填入数字 9。

当 k 的值等于 8 时,最低三位是 8 的倍数时才能被 k 整除,符合要求的最高三位的最大值是 888。需要考虑 n<=6 和 n>6 的情况。

当 n<=6 时,所有数位填入数字 8。

当 n>6 时,最高三位和最低三位填入数字 8,其余数位填入数字 9。

当 k 的值等于 6 时,需要考虑 n<=2 和 n>2 的情况。

当 n<=2 时,所有数位填入数字 6。

当 n>2 时,由于最低位必须是偶数因此最高位和最低位填入数字 8,当 n 是奇数时中间一位填入数字 8,当 n 是偶数时中间两位填入数字 7,除了最高位、最低位和中间位(中间位最多两位)以外,其余数位填入数字 9。

当 k 的值等于 7 时,需要考虑 n<=2 和 n>2 的情况。

当 n<=2 时,所有数位填入数字 7。

当 n>2 时,首先计算所有数位填入数字 9 的整数是否可以被 7 整除,如果可以整除则得到答案,如果不能整除则考虑将中间位调整,当 n 是奇数时调整中间一位,当 n 是偶数时调整中间两位,寻找可以被 7 整除的最大整数。记 d=⌊(n−1)/2⌋,则当 n 是奇数时每次将中间一位减少 1 都会使整数减少 1×10^d,当 n 是偶数时每次将中间两位减少 1 都会使整数减少 11×10^d,减少量与 7 互质,因此最多调整 6 次一定可以得到 7 的倍数。

具体代码如下:

class Solution {
    public String largestPalindrome(int n, int k) {
        char[] arr = new char[n];
        Arrays.fill(arr, '9');
        if (k == 2) {
            arr[0] = arr[n - 1] = '8';
        } else if (k == 5) {
            arr[0] = arr[n - 1] = '5';
        } else if (k == 4) {
            if (n <= 4) {
                Arrays.fill(arr, '8');
            } else {
                arr[0] = arr[n - 1] = '8';
                arr[1] = arr[n - 2] = '8';
            }
        } else if (k == 8) {
            if (n <= 6) {
                Arrays.fill(arr, '8');
            } else {
                arr[0] = arr[n - 1] = '8';
                arr[1] = arr[n - 2] = '8';
                arr[2] = arr[n - 3] = '8';
            }
        } else if (k == 6) {
            if (n <= 2) {
                Arrays.fill(arr, '6');
            } else {
                arr[0] = arr[n - 1] = '8';
                int midIndex = n / 2;
                if (n % 2 != 0) {
                    arr[midIndex] = '8';
                } else {
                    arr[midIndex - 1] = arr[midIndex] = '7';
                }
            }
        } else if (k == 7) {
            if (n <= 2) {
                Arrays.fill(arr, '7');
            } else {
                int originalRemainder = 0;
                for (int i = 0; i < n; i++) {
                    originalRemainder = (originalRemainder * 10 + 9) % 7;
                }
                if (originalRemainder != 0) {
                    int midRemainder = n % 2 != 0 ? 1 : 11;
                    int[] replaceIndices = n % 2 != 0 ? new int[]{n / 2} : new int[]{n / 2 - 1, n / 2};
                    int digitsCount = (n - 1) / 2;
                    for (int i = 0; i < digitsCount; i++) {
                        midRemainder = midRemainder * 10 % 7;
                    }
                    boolean flag = false;
                    for (int digit = 8; digit >= 0 && !flag; digit--) {
                        originalRemainder = (originalRemainder - midRemainder) % 7;
                        if (originalRemainder < 0) {
                            originalRemainder += 7;
                        }
                        if (originalRemainder == 0) {
                            for (int replaceIndex : replaceIndices) {
                                arr[replaceIndex] = (char) ('0' + digit);
                            }
                            flag = true;
                        }
                    }
                }
            }
        }
        return new String(arr);
    }
}

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
  • 所有查询互不相同

思路:由于自己的思路就是简单暴力,也一直超时,以下是灵神的解法。

核心思路:对于每个询问,计算以 l 为右端点的合法子串个数,以 l+1 为右端点的合法子串个数,……,以 r 为右端点的合法子串个数。

我们需要知道以 i 为右端点的合法子串,其左端点最小是多少。

由于随着 i 的变大,窗口内的字符数量变多,越不能满足题目要求,所以最小左端点会随着 i 的增大而增大,有单调性,因此可以用 滑动窗口 计算。

设以 i 为右端点的合法子串,其左端点最小是 left[i]。

那么以 i 为右端点的合法子串,其左端点可以是 left[i],left[i]+1,⋯,i,一共i−left[i]+1个。

回答询问时,分类讨论:

  1. 如果 left[r]≤l,说明 [l,r] 内的所有子串都是合法的,这一共有 1+2+⋯+(r−l+1)= 

(r−l+2)(r−l+1)/2个。

     2.否则,由于 left 是有序数组,我们可以在 [l,r] 中 二分查找 left 中的第一个满足 left[j]≥l 的下标 j,那么:由于 left[j−1]<l,所以 [l,j−1] 内的所有子串都是合法的,这一共有 1+2+⋯+(j−l)= 
(j−l+1)(j−l)/2个。
     右端点在 [j,r] 内的子串,可以累加下标在 [j,r] 内的所有 i−left[i]+1 的和。这可以用 前缀和 预处理。
     上述两项累加,即为答案。
    

class Solution {
    public long[] countKConstraintSubstrings(String S, int k, int[][] queries) {
        char[] s = S.toCharArray();
        int n = s.length;
        int[] left = new int[n];
        long[] sum = new long[n + 1];
        int[] cnt = new int[2];
        int l = 0;
        for (int i = 0; i < n; i++) {
            cnt[s[i] & 1]++;
            while (cnt[0] > k && cnt[1] > k) {
                cnt[s[l++] & 1]--;
            }
            left[i] = l;
            // 计算 i-left[i]+1 的前缀和
            sum[i + 1] = sum[i] + i - l + 1;
        }

        long[] ans = new long[queries.length];
        for (int i = 0; i < queries.length; i++) {
            int ql = queries[i][0];
            int qr = queries[i][1];
            int j = lowerBound(left, ql - 1, qr + 1, ql);
            ans[i] = sum[qr + 1] - sum[j] + (long) (j - ql + 1) * (j - ql) / 2;
        }
        return ans;
    }

    // 返回在开区间 (left, right) 中的最小的 j,满足 nums[j] >= target
    // 如果没有这样的数,返回 right
    // 原理见 https://www.bilibili.com/video/BV1AP41137w7/
    private int lowerBound(int[] nums, int left, int right, int target) {
        while (left + 1 < right) { // 区间不为空
            // 循环不变量:
            // nums[left] < target
            // nums[right] >= target
            int mid = left + (right - left) / 2;
            if (nums[mid] < target) {
                left = mid; // 范围缩小到 (mid, right)
            } else {
                right = mid; // 范围缩小到 (left, mid)
            }
        }
        return right;
    }
}

  • 18
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值