给你一个 二进制 字符串 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;
}
}
}
来自未来的体育科学家给你两个整数数组 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]);
}
}
给你两个 正整数 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);
}
}
给你一个 二进制 字符串 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个。
回答询问时,分类讨论:
- 如果 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;
}
}