[动态规划]:332.二进制字符串单调递增问题

120 阅读2分钟

问题描述

小S有一个二进制字符串 s,他可以将任意 0 翻转为 1,或将 1 翻转为 0,以使字符串变为单调递增的形式。一个字符串是单调递增的,当它是由一些 0(可能没有 0)跟随一些 1(可能没有 1)组成的。

你的任务是帮助小S计算使字符串单调递增的最小翻转次数。

例如:当 s = "00110" 时,最小的翻转次数是 1,可以将第二个 1 翻转为 0 使字符串变为 "00010"


测试样例

样例1:

输入:s = "00110" 输出:1

样例2:

输入:s = "010110" 输出:2

样例3:

输入:s = "00011000" 输出:2

样例4:

输入:s = "11100" 输出:2

解题思路

  • 由于可以0110,我们考虑字符串以0结尾和以1结尾的情况,两种情况取最小变化

  • f[i][0]f[i][0]表示在第ii个字符时,以0结尾的最小变化数量

  • f[i][1]f[i][1]表示在第ii时,以1结尾的最小变化数量

  • 如果当前字符si=0s_i=0

    • f[i][0]=f[i1][0]f[i][0] = f[i-1][0] ,由f[i1]f[i-1]0结尾的次数转换过来
    • f[i][1]=min(f[i1][0],f[i1][1])+1f[i][1] = min(f[i-1][0], f[i-1][1]) +1, 由f[i1]f[i-1]的最小次数 + 1(当前字符变为1)转换
  • 如果当前字符si=1s_i=1

    • f[i][0]=f[0]+1f[i][0] = f[0] + 1 ,只能由f[i1]f[i-1]0结尾的次数+1转换过来,因为要求单调递增
    • f[i][1]=min(f[i1][0],f[i1][1])f[i][1] = min(f[i-1][0], f[i-1][1]), 得到前面的最小转换次数,当前字符不需要转换
  • 最后取min(f[n][0],f[n][1])min(f[n][0],f[n][1])即可

核心代码

int solution(const string& s) {
    int n = s.length();
    vector<vector<int>> f(n + 1, vector<int>(2, 0));

    for (int i = 1; i <= n; i++) {
        if (s[i - 1] == '0') {
            f[i][0] = f[i - 1][0];  
            f[i][1] = min(f[i - 1][0], f[i - 1][1]) + 1;  
        } else {
            f[i][0] = f[i - 1][0] + 1;  
            f[i][1] = min(f[i - 1][0], f[i - 1][1]); 
        }
    }
    
    return min(f[n][0], f[n][1]);
}

优化

  • 可以看出当前f[i]f[i]只依赖于f[i1]f[i-1],我们可以用两个变量来代替

  • f0f0表示以0结尾的最小转换次数, f1f1表示以1结尾的最小转换次数

  • si=0s_i = 0时,

    • f1f1min(f0,f1)+1min(f0 , f1) + 1得到
    • f0f0不变
  • si=1s_i = 1时,

    • f1=min(f0,f1)f1 = min(f0, f1)
    • f0++f0++ ,这一步要在后面,因为f1f1的变化依赖于f0f0

优化代码

int solution(const string& s) {
    int n = s.length();
    int f0 = 0, f1 = 0;
    for(int i=0; i < n; i++) {
        f1 = min(f0, f1) + (s[i] == '0');
        f0 += s[i] == '1';
    }
    return min(f0, f1);
}