Bootstrap

【每日练习】将字符串翻转到单调递增、使字符串平衡的最少删除次数

将字符串翻转到单调递增

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;
    }
};
;