递推都来了,动态规划还会远吗?


欢迎加入万人千题社区与英雄哥一起打卡
这是今天的算法打卡训练
《算法零基础100讲》(第28讲) 递推问题

简单讲一下最后一题吧

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

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

如果一个由 ‘0’ 和 ‘1’ 组成的字符串,是以一些 ‘0’(可能没有 ‘0’)后面跟着一些 ‘1’(也可能没有 ‘1’)的形式组成的,那么该字符串是单调递增的。 我们给出一个由字符 ‘0’ 和 ‘1’ 组成的字符串 S,我们可以将任何 ‘0’ 翻转为 ‘1’ 或者将 ‘1’ 翻转为 ‘0’。返回使 S 单调递增的最小翻转次数。

示例 1: 输入:“00110” 输出:1 解释:我们翻转最后一位得到 00111.

示例 2: 输入:“010110” 输出:2 解释:我们翻转得到 011111,或者是 000111。

基本思路:熟悉的动态规划,没有思考过状态方程这玩意可能比较难有思路。我们知道每一位可以选择翻转与不翻转,取决于前一位的字符,比如前一位是 0,当前位为 0 或 1都不需要翻转 ,如果前一位为 1,当前为为1则不需要,为0则需要翻转。这是翻转的策略,同时我们还要需要记录最小的翻转策略。

我们两个数组,zero[i]和one[i] 分别表示当前下标字符为 0 或 1的递增串的最小翻转次数 。

为什么要选两个不直接选一个数组记录呢。因为我们的最优情况有3种全为0或者全为1或者由01构成那么当数组遍历到最后一位,zero[i]就包含了全为0的情况,而one[i]包含另外两种情况,当然后面我们也可以简化成两个临时变量来记录。
我们设出递归数组,再看看状态方程

S [ i ] = 0    ⟹    { z e r o [ i ] = z e r o [ i − 1 ]   S [ i − 1 ] = 0   z e r o [ i ] = z e r o [ i − 1 ]   S [ i − 1 ] = 1   S[i] = 0\implies\begin{cases} zero[i] = zero[i-1] & \text { $S[i-1]=0$ } \\zero[i] = zero[i-1] & \text{ $ S[i-1]=1$ } \end{cases} S[i]=0{zero[i]=zero[i1]zero[i]=zero[i1] S[i1]= S[i1]=

解释:
如果当前下标i元素为0,那么zero[i]代表长度为i-1全部为0的的递增字符串的最小翻转次数。而zero[ i-1]代表长度为i-1全部为0的的递增字符串的最小翻转次数,当前字符为0不用翻了老铁们。所以直接赋值。

S [ i ] = 1    ⟹    { z e r o [ i ] = z e r o [ i − 1 ] + 1   S [ i − 1 ] = 0   z e r o [ i ] = z e r o [ i − 1 ] + 1   S [ i − 1 ] = 1   S[i] = 1\implies\begin{cases} zero[i] = zero[i-1] +1 & \text { $S[i-1]=0$ } \\zero[i] = zero[i-1] +1 & \text{ $ S[i-1]=1$ } \end{cases} S[i]=1{zero[i]=zero[i1]+1zero[i]=zero[i1]+1 S[i1]= S[i1]=

解释:
和前面差不多只不过当前为1所以要翻一次,zero情况是比较特殊的,因为翻转全为0,所以不用考虑one的情况

S [ i ] = 0    ⟹    { o n e [ i ] = m i n ( z e r o [ i − 1 ] + 1 , o n e [ i − 1 ] + 1 )   S [ i − 1 ] = 0   o n e [ i ] = m i n ( z e r o [ i − 1 ] + 1 , o n e [ i − 1 ] + 1 )   S [ i − 1 ] = 1   S[i] = 0\implies\begin{cases} one[i] =min(zero[i-1] + 1,one[i-1] + 1) & \text { $S[i-1]=0$ } \\one[i] = min(zero[i-1] + 1,one[i-1] + 1)& \text{ $ S[i-1]=1$ } \end{cases} S[i]=0{one[i]=min(zero[i1]+1,one[i1]+1)one[i]=min(zero[i1]+1,one[i1]+1) S[i1]= S[i1]=

解释:
如果当前为0,那么one[i]代表长度为i-1的且当前下标字符翻转后为1的递增字符串的最小翻转次数。那么我们必然要翻转+1,那么考虑到上一个字符翻转后是0或1,我们只需要记得我们要求的是最小翻转次数,那么只需要比较上一个字符翻转为0或者1的最小翻转次数就好
即min(zero[i-1] + 1,one[i-1] + 1)

S [ i ] = 1    ⟹    { o n e [ i ] = m i n ( z e r o [ i − 1 ] , o n e [ i − 1 ] )   S [ i − 1 ] = 0   o n e [ i ] = m i n ( z e r o [ i − 1 ] , o n e [ i − 1 ] )   S [ i − 1 ] = 1   S[i] = 1\implies\begin{cases} one[i] =min(zero[i-1] ,one[i-1] ) & \text { $S[i-1]=0$ } \\one[i] =min(zero[i-1] ,one[i-1] ) & \text{ $ S[i-1]=1$ } \end{cases} S[i]=1{one[i]=min(zero[i1],one[i1])one[i]=min(zero[i1],one[i1]) S[i1]= S[i1]=
解释:
当前字符为1,那么one不需要翻转,老规矩,直接 min(one[i-1],zero[i-1]);
最终循环到i-1 只需要比较one[i-1]和zero[i-1]即可

同时我们可以发现数组之间的关系只有当前与前一个,那么我们可以设临时变量节省空间了
设一个zero和one 那么状态方程

S [ i ] = 0    ⟹    z e r o = z e r o    ⟹    { z e r o [ i ] = z e r o [ i − 1 ]   S [ i − 1 ] = 0   z e r o [ i ] = z e r o [ i − 1 ]   S [ i − 1 ] = 1   S[i] = 0\implies zero = zero \implies \begin{cases} zero[i] = zero[i-1] & \text { $S[i-1]=0$ } \\zero[i] = zero[i-1] & \text{ $ S[i-1]=1$ } \end{cases} S[i]=0zero=zero{zero[i]=zero[i1]zero[i]=zero[i1] S[i1]= S[i1]=

S [ i ] = 1    ⟹    z e r o = z e r o + 1    ⟹    { z e r o [ i ] = z e r o [ i − 1 ] + 1   S [ i − 1 ] = 0   z e r o [ i ] = z e r o [ i − 1 ] + 1   S [ i − 1 ] = 1   S[i] = 1\implies zero=zero+1 \implies \begin{cases} zero[i] = zero[i-1] +1 & \text { $S[i-1]=0$ } \\zero[i] = zero[i-1] +1 & \text{ $ S[i-1]=1$ } \end{cases} S[i]=1zero=zero+1{zero[i]=zero[i1]+1zero[i]=zero[i1]+1 S[i1]= S[i1]=

S [ i ] = 0    ⟹    o n e = m i n ( z e r o + 1 , o n e + 1 )    ⟹    { o n e [ i ] = m i n ( z e r o [ i − 1 ] + 1 , o n e [ i − 1 ] + 1 )   S [ i − 1 ] = 0   o n e [ i ] = m i n ( z e r o [ i − 1 ] + 1 , o n e [ i − 1 ] + 1 )   S [ i − 1 ] = 1   S[i] = 0\implies one = min(zero + 1,one + 1)\implies \begin{cases} one[i] =min(zero[i-1] + 1,one[i-1] + 1) & \text { $S[i-1]=0$ } \\one[i] = min(zero[i-1] + 1,one[i-1] + 1)& \text{ $ S[i-1]=1$ } \end{cases} S[i]=0one=min(zero+1,one+1){one[i]=min(zero[i1]+1,one[i1]+1)one[i]=min(zero[i1]+1,one[i1]+1) S[i1]= S[i1]=

S [ i ] = 1    ⟹    o n e = m i n ( z e r o , o n e )    ⟹    { o n e [ i ] = m i n ( z e r o [ i − 1 ] , o n e [ i − 1 ] )   S [ i − 1 ] = 0   o n e [ i ] = m i n ( z e r o [ i − 1 ] , o n e [ i − 1 ] )   S [ i − 1 ] = 1   S[i] = 1\implies one = min(zero ,one ) \implies \begin{cases} one[i] =min(zero[i-1] ,one[i-1] ) & \text { $S[i-1]=0$ } \\one[i] =min(zero[i-1] ,one[i-1] ) & \text{ $ S[i-1]=1$ } \end{cases} S[i]=1one=min(zero,one){one[i]=min(zero[i1],one[i1])one[i]=min(zero[i1],one[i1]) S[i1]= S[i1]=
所以重复不变的直接忽略,精简代码

class Solution {
public:
    int minFlipsMonoIncr(string s) {
          int zero, one;
        if(s.size()==1)  return 0;
        if(s[0]=='0'){
            zero = 0;
            one = 1;
        }else {
            zero = 1;
            one = 0;
        }
        for(int i=1;i<s.size();i++){
            if(s[i]=='0'){
                if(s[i-1]=='0')  zero = zero;
                one = min(zero + 1,one + 1);
            }else{
                if(s[i-1]=='0')    one = min(one,zero);   
               zero = zero + 1;  
            }
        }
        return min(zero,one);
    }
};

再看来看看磊哥的思路
是个大神大家可以关注一下
磊哥yyds

int minFlipsMonoIncr(char * s){
    if(strlen(s) == 1)  return 0;
    int count = 0,min = 0;
    for(int i = 0;s[i];i++)
        if(s[i] == '0') count++;//统计在最前面反转的次数
    min = count;    //min初始值
    for(int i = 0;s[i];i++){
        if(s[i] == '0') count--;
        else count++;    //更新count
        min = min>count?count:min;//更新min
    }
    return min;
}

磊哥主要的思路呢。就是先把全部翻成1,也就是遇到0,你翻的次数就加1,为什么这样呢,因为这样我们假设全部为1之后,我们再尝试把位于前排的0不动,位于0后面的1翻为0,使得我们可能(注意是可能)找到一个0000111的翻转次数比全部翻为1小。
因此就有了代码
for(int i = 0;s[i];i++){
if(s[i] == ‘0’) count–;
else count++; //更新count
min = min>count?count:min;//更新min
}
这个过程当我们遍历到结尾已经全部变为0了,因此我们就考虑到了全为1,全为0,以及01的情况,并实施更新最小值,相当秒啊
请添加图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值