前言
记录Leecode的解题过程是有益的,不记录思考中碰到的问题的话,就会减慢解题速度甚至将之前做的题忘记。模板确实是一个好的办法,不会模板不行,只会模板也不行。只看标签写问题直接套模板自然也不行。因此我将我做题的思路写出来,包括正确的和错误的,以供大家参考与指正。一般是分析每日一题。
926. 将字符串翻转到单调递增
如果一个二进制字符串,是以一些 0(可能没有 0)后面跟着一些 1(也可能没有 1)的形式组成的,那么该字符串是 单调递增 的。
给你一个二进制字符串 s,你可以将任何 0 翻转为 1 或者将 1 翻转为 0 。
返回使 s 单调递增的最小翻转次数。
思路分析
本题的难点在于最小,怎么确认是最小的反转次数,需要保证前面是0后面是1
思路 既然有一半为0一般为1,从前往后到全为0的翻转数且连续,再计算从后往前到全为1到翻转数,计算同一下标加起来最小的值。
int minFlipsMonoIncr(string s) {
int n=s.size();
vector<int> dp0(n,0),dp1(n,0);
dp0[0]=s[0]=='1'?1:0;//定义初值若第一位为1则翻转数初始为1
dp1[n-1]=s[n-1]=='0'?1:0;//定义初值最后一位为0则翻转数为0
for(int i=1;i<n;i++){
dp0[i]=s[i]=='1'?dp0[i-1]+1:dp0[i-1];
dp1[n-1-i]=s[n-i-1]=='0'?dp1[n-i]+1:dp1[n-i];
}
int ans=INT_MAX;
for(int i=0;i<n;i++){
ans=min(ans,dp0[i]+dp1[i]);
}
return ans-1;//由于重复计算了一次分割点
}
但是时间空间复杂度都O(N),打败了30左右的用户明显不太行
官方思路:
官方思路的上述两个dp都是从前往后计算的,dp0与直观分析一样但dp1却略有不同
public:
int minFilpsMonoIncr(string &s){
int dp0=0,dp1=0;
for(char c:s){
int dp0New =dp0;//dp0沿用上面的方案
dp1New = min(dp0,dp1);//dp1通过两个值来判断是否为分段点
if(c=='1'){
dp0New++;
}else{
dp1New++;
}
dp0=dp0New;
dp1=dp1New;
}
return min(dp0,dp1);//返回最小值
}
这一题的难点在于dp1New=min(dp0,dp1)。官方的思路是将上面的数组转换成了第i个字符是0的变换次数。其实也就说明了如果前面都是0的化则为000 (1)11的分界点(即dp0+1)是可行的。若前面已经有了分界点,则变为之前的0001(1)1继续延续下去。
这种思路可能没有这么直观,但是为了简化多往同一个方向上完成动态规划去考虑。
总结
本题意指找出最小的值,若真要进行搜索,本题数据范围较大,且最终结果不够清晰,需要找到最小翻转形态,与最小翻转次数,因此极为麻烦。可以跳过这种想法。因此本题应偏向于动态规划,原本的思路最终的答案可能与每一个之前的状态有关,不能进行进一步优化,至于动态规划滚动数组来减小空间时间复杂度是需要掌握的。
至于动态规划的题目最重要的是思考数组单个数据所蕴含的意义,本题可以由dp0的意义推测dp1的意义,再根据意义来推断dp1的求取的方式