[LeetCode]926. 将字符串翻转到单调递增

75 篇文章 0 订阅

题目

926. 将字符串翻转到单调递增
如果一个二进制字符串,是以一些 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'


方法1:前缀和

       public int minFlipsMonoIncr(String s) {
            int n = s.length();
            //zero 统计0的前缀和 one 统计1的前缀和
            int[] zero = new int[n + 1], one = new int[n + 1];
            for (int i = 0; i < n; i++) {
                char c = s.charAt(i);
                if (c == '0') {
                    zero[i + 1] = zero[i] + 1;
                    one[i + 1] = one[i];
                } else {
                    one[i + 1] = one[i] + 1;
                    zero[i + 1] = zero[i];
                }
            }
            int res = s.length();
            for (int i = 1; i <= n; i++) {
                //前部分1的个数  后部分0的个数
                int pre_one = one[i], suc_zero = zero[n] - zero[i];
                //将前部分的1都变成0 后部分的0都变成1 形如00000111这样的结果
                res = Math.min(res, pre_one + suc_zero);
                //前部分0的个数
                int pre_zero = zero[i];
                //将前部分的0都变成1 后部分的0都变成1 形如111111111这样的结果
                res = Math.min(res, pre_zero + suc_zero);
            }
            return res;
        }
  • 另,下面的是官方的解,说不上来哪里不一样,官方解执行了上面代码的前一种case,即将前部分的1都变成0 后部分的0都变成1 形如00000111这样的结果
    public int minFlipsMonoIncr(String S) {
        int N = S.length();
        int[] P = new int[N + 1];
        for (int i = 0; i < N; ++i)
            P[i+1] = P[i] + (S.charAt(i) == '1' ? 1 : 0);

        int ans = Integer.MAX_VALUE;
        for (int j = 0; j <= N; ++j) {
            ans = Math.min(ans, P[j] + N-j-(P[N]-P[j]));
        }

        return ans;
    }

方法2:朴素版DP

        public int minFlipsMonoIncr(String s) {
            int n = s.length();
            //dp[i][0]表示前i个元素,最后一个元素为0的最小翻转次数;
            //dp[i][1]表示前i个元素,最后一个元素为1的最小翻转次数
            int[][] dp = new int[n][2];
            //初始化
            char c0 = s.charAt(0);
            //第一个字符本身是'0'的话,不需要翻转,如果是'1'需要执行一次翻转
            dp[0][0] = c0 == '0' ? 0 : 1;
            //第一个字符是'1'的话,不需要翻转,如果是'0'的话,需要执行一次翻转
            //鉴于下面的转移会拿dp[i-1][0] 和 dp[i-1][1]的最小值,这个最小值肯定为0,对于[0]这个元素来说
            //下面的这段初始化可以省略,设置为默认值0
//            dp[0][1] = c0 == '1' ? 0 : 1;
            for (int i = 1; i < n; i++) {
                char c = s.charAt(i);
                //注意优先级
                //前[i-1]个元素必须要都是0,c如果是'1'要执行一次翻转
                dp[i][0] = dp[i - 1][0] + (c == '0' ? 0 : 1);
                //前[i-1]可以都是0 可以以'1'结束,c如果是'0',要进行一次翻转
                dp[i][1] = Math.min(dp[i - 1][1], dp[i - 1][0]) + (c == '1' ? 0 : 1);
            }
            //总的区间,取最终变成 000000这种格式还是000011这种格式
            return Math.min(dp[n - 1][0], dp[n - 1][1]);
        }

方法3:空间压缩DP

        //[i]的状态只和[i-1]即前一个状态有关,可以进行空间优化
        public int minFlipsMonoIncr(String s) {
            int n = s.length();
            //当前下标i下字符为0和1的情况下,使得s[0...i]单调递增的最小翻转次数
            int f0 = 0, f1 = 0;
            for (int i = 0; i < n; i++) {
                //下一轮的 f0 f1
                int _f0 = f0, _f1 = Math.min(f0, f1);
                if (s.charAt(i) == '1') {//当前下标i的字符是'1'
                    _f0++;
                } else {
                    _f1++;
                }
                f0 = _f0;
                f1 = _f1;
            }
            return Math.min(f0, f1);
        }

方法4:LIS+二分

一些名词
  • 最长上升子序列( L I S LIS LIS):Longest Increasing Subsequence
  • 最长连续序列( L C S LCS LCS):Longest Consecutive Sequence
  • 最长连续递增序列( L C I S LCIS LCIS):Longest Continuous Increasing Subsequence
  • 最长公共子序列( L C S LCS LCS):Longest Common Subsequence

子串与子序列区别:子串不可跳跃,子序列可以跳跃,如 “AC”是“ABCDEFG”的子序列,而不是子串。 而“ABC”则是其子串

  • 模板题是300题
        public int minFlipsMonoIncr(String s) {
            int n = s.length();
            //维护一个单调递增的tails数组 形如 00011
            char[] tails = new char[n];
            int index = 0;//tails数组当前的处理到的位置
            for (char x : s.toCharArray()) {
                int i = 0, j = index;//
                while (i < j) {
                    int mid = i + (j - i) / 2;
                    //如果当前的x比tails[mid]大,说明应该放在mid的右侧位置,否则放在左侧位置
                    if (tails[mid] <= x) i = mid + 1;
                    else j = mid;
                }
                tails[i] = x;//x放置的位置
                //如果放在末尾,index要偏移一个值
                if (j == index) index++;
            }
            //index表示 单调递增的长度(恰好在上面的操作中+1了),剩下的n -index就是要翻转的字符,即翻转的操作数
            return n - index;
        }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值