递推问题
欢迎加入万人千题社区与英雄哥一起打卡
这是今天的算法打卡训练
《算法零基础100讲》(第28讲) 递推问题
简单讲一下最后一题吧
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[i−1]zero[i]=zero[i−1] S[i−1]=0 S[i−1]=1
解释:
如果当前下标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[i−1]+1zero[i]=zero[i−1]+1 S[i−1]=0 S[i−1]=1
解释:
和前面差不多只不过当前为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[i−1]+1,one[i−1]+1)one[i]=min(zero[i−1]+1,one[i−1]+1) S[i−1]=0 S[i−1]=1
解释:
如果当前为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[i−1],one[i−1])one[i]=min(zero[i−1],one[i−1]) S[i−1]=0 S[i−1]=1
解释:
当前字符为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]=0⟹zero=zero⟹{zero[i]=zero[i−1]zero[i]=zero[i−1] S[i−1]=0 S[i−1]=1
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]=1⟹zero=zero+1⟹{zero[i]=zero[i−1]+1zero[i]=zero[i−1]+1 S[i−1]=0 S[i−1]=1
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]=0⟹one=min(zero+1,one+1)⟹{one[i]=min(zero[i−1]+1,one[i−1]+1)one[i]=min(zero[i−1]+1,one[i−1]+1) S[i−1]=0 S[i−1]=1
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]=1⟹one=min(zero,one)⟹{one[i]=min(zero[i−1],one[i−1])one[i]=min(zero[i−1],one[i−1]) S[i−1]=0 S[i−1]=1
所以重复不变的直接忽略,精简代码
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的情况,并实施更新最小值,相当秒啊