【每日练习】将字符串翻转到单调递增、使字符串平衡的最少删除次数
将字符串翻转到单调递增
https://leetcode.cn/problems/flip-string-to-monotone-increasing/
如果一个二进制字符串,是以一些 0
(可能没有 0
)后面跟着一些 1
(也可能没有 1
)的形式组成的,那么该字符串是 单调递增 的。
给你一个二进制字符串 s
,你可以将任何 0
翻转为 1
或者将 1
翻转为 0
。
返回使 s
单调递增的最小翻转次数。
示例 1:
输入:s = "00110"
输出:1
解释:翻转最后一位得到 00111.
示例 2:
输入:s = "010110"
输出:2
解释:翻转得到 011111,或者是 000111。
示例 3:
输入:s = "00011000"
输出:2
解释:翻转得到 00000000。
提示:
1 <= s.length <= 105
s[i]
为'0'
或'1'
思路
我们可以将字符串 s 分为两部分:前面的 0 和后面的 1。假设前面有 x 个 0,后面有 y 个 1,则原始的字符串可以表示为:
000...0111...111
我们需要找到最少的翻转次数,将其变为一个单调递增的字符串。考虑两种情况:
- 将前面所有的 0 都变为 1。此时需要翻转 x 次。
- 将后面所有的 1 都变为 0。此时需要翻转 y 次。
我们需要取这两种情况中的最小值,因为我们希望翻转次数最小。
具体实现时,我们遍历字符串 s,统计出前面的 0 的数量和后面的 1 的数量,并用变量 flips 记录当前已经进行的翻转次数。在遍历过程中,如果遇到了 '0',则将 flips 加 1,表示将该 '0' 翻转为 '1'。否则,表示遇到了 '1',那么就不需要进行翻转操作,只需要将后面的 1 的数量减 1 即可。
在遍历过程中,我们需要维护一个变量 ones,表示后面剩余的 1 的数量。将 flips 和 ones 取最小值即为当前的最小翻转次数。最后返回这个最小值即可。
以示例 1 "00110" 为例,遍历过程如下:
s: 0 0 1 1 0
| | |
x y ones
flips = 0, ones = 2
遇到 '0',将 flips 加 1,ones 不变,即 flips = 1,ones = 2
flips = 1, ones = 2
遇到 '0',将 flips 加 1,ones 不变,即 flips = 2,ones = 2
flips = 2, ones = 2
遇到 '1',将 ones 减 1,flips 不变,即 flips = 2,ones = 1
flips = 2, ones = 1
遇到 '1',将 ones 减 1,flips 不变,即 flips = 2,ones = 0
最后返回 flips,即 2。
根据结果可以看出,我们需要将前面的两个 '0' 翻转为 '1' 才能得到一个单调递增的字符串。
代码
class Solution {
public:
int minFlipsMonoIncr(string s) {
int ones = 0, flips = 0;//flips记录当前已经进行的翻转次数
for (char c : s) {
if (c == '0') {
// 将当前的 '0' 变为 '1'
flips++;
} else {
// 统计当前出现的 '1' 的数量
ones++;
}
// 将 '0' 的数量与剩余的 '1' 的数量比较,取最小值
flips = min(flips, ones);
}
return flips;
}
};
使字符串平衡的最少删除次数
https://leetcode.cn/problems/minimum-deletions-to-make-string-balanced/
给你一个字符串 s
,它仅包含字符 'a'
和 'b'
。
你可以删除 s
中任意数目的字符,使得 s
平衡 。当不存在下标对 (i,j)
满足 i < j
,且 s[i] = 'b'
的同时 s[j]= 'a'
,此时认为 s
是 平衡 的。
请你返回使 s
平衡 的 最少 删除次数。
示例 1:
输入:s = "aababbab"
输出:2
解释:你可以选择以下任意一种方案:
下标从 0 开始,删除第 2 和第 6 个字符("aababbab" -> "aaabbb"),
下标从 0 开始,删除第 3 和第 6 个字符("aababbab" -> "aabbbb")。
示例 2:
输入:s = "bbaaaaabb"
输出:2
解释:唯一的最优解是删除最前面两个字符。
提示:
1 <= s.length <= 105
s[i]
要么是'a'
要么是'b'
。
思路
题意本质上是要求输入字符串s中b不能够出现在a的前面,因此采用遍历两次的方式来做,与前面一题稍微不同
维护两个变量,res代表需要进行输出操作的次数,count4a是a出现的次数统计
第一次遍历s,我们先统计出a的数量。然后将a的数量作为删除次数的初始值
因为我们要使得字符串"平衡",一种方式是将字符串中的a全部变成b(或者把b全部变成a,两种一样)
所以这里统计出a的数量就相当于要将a变成b的次数,该次数可以作为删除操作的初始值
第二次遍历我们是要找出最少的删除操作次数,因为第一次遍历结束后我们得到的是两种情况(或者说解法):即把a全部变成b或者把b全部变成a。这两种方法虽然都是本题的一个解,但不是最优的。
因为实际上我们只需要保证b不出现在a之前就行,那么只要在遇到ba的时候把其中一个改了就可以(改谁其实无所谓)
所以,在第二次遍历的时候,我们在第一次遍历得到的解(把b全部改成a)的基础上继续优化,也就是遇到ba才改,其余情况不改
那么当再次遍历到a的时候,此时作为修改次数的count4a变量(因为要用count4a更新res,所以其也代表修改次数)要减1,用来表示此时我们不需要修改
当再次遍历遇到b的时候,此时不论后面是bb还是ba,都要进行修改操作,所以修改计数count4a要加1
每次遍历我们都更新res值,最后可以得到一个最少的修改次数
代码
#include <string>
class Solution {
public:
int minimumDeletions(std::string s) {
int res = 0, count4a = 0;//count代表a出现的次数,res代表需要进行删除操作的次数
for(char c : s){ //统计一下a出现的次数
if(c == 'a') count4a++;
}
//需要将res初始化为count,表示初始状态下的最少删除次数。
res = count4a;
for(char c : s){
if(c == 'a') count4a--;
else count4a++;
res = min(res, count4a);
}
return res;
}
};