题目


解题思路

一个很暴力的想法,在满足单调递增的前提下,使每一位分别取 1 或 0,去看看哪个结果小。

递归函数定义int dp(StringBuilder sb, int ind, int pre) sb是字符串,ind 是字符串当前位,pre 是字符串前一位(0或1)

dp函数表示:从字符串当前位ind开始到字符串结尾,这样一个子字符串,变为单调递增所需要翻转的最小次数。

因此题目所求就是 dp(sb, 0, 0)。第0位的前一位为0。

具体递归入口有四种情况,根据前一位是0或1 和 当前位是0或1来讨论,即

当前位为0:
前一位为0: 前一位为1: 当前位为1:
前一位为0: 前一位为1:

一开始递归函数定义不明确,导致无法形成重叠子问题,也就无法用备忘录来优化。
在考虑备忘录优化的时候,需要明确备忘录的每一维分别代表什么。参考了这位大佬的题解Java 递归+二维DP+空间优化

代码

class Solution {
int[][] memo; // 备忘录
public int minFlipsMonoIncr(String s) {
int n = s.length();
memo = new int[n + 1][2];
for(int[] arr : memo){
Arrays.fill(arr, -1);
}
return dp(s.toCharArray(), 0, 0);
} int dp(char[] s, int ind, int pre){ // sb是字符串,ind 是字符串当前位,pre 是前一位
if(ind == s.length){ // 到字符串结尾了,需要改变的字符为0,返回值为0
return 0;
}
if(memo[ind][pre] != -1){
return memo[ind][pre];
} int res = 0; if(s[ind] == '0'){ // 当前位为0
if(pre == 0){ // 前一位为 0
int a = dp(s, ind + 1, s[ind] - '0'); // 保持0不变
s[ind] = '1';
int b = dp(s, ind + 1, s[ind] - '0'); // 把当前0变为1,翻转次数加一
s[ind] = '0';
res += Math.min(a, b); // 取两者中最小的情况
}else if(pre == 1){ // 前一位为 1
s[ind] = '1';
res += dp(s, ind + 1, s[ind] - '0') + 1; // 前一位为1,当前位为0,必须变成1
s[ind] = '0';
}
}else if(s[ind] == '1'){ // 当前位为1
if(pre == 0){ // 前一位为 0
int a = dp(s, ind + 1, s[ind] - '0'); // 保持1不变
s[ind] = '0';
int b = dp(s, ind + 1, s[ind] - '0') + 1; // 把当前1变为0,翻转次数加一
s[ind] = '1';
res += Math.min(a, b); // 取两者中最小的情况
}else if(pre == 1){
res += dp(s, ind + 1, s[ind] - '0'); // 前一位为1,当前位为1,当前1必须保持不变
}
} memo[ind][pre] = res;
return res;
}
}

优化后

class Solution {
int[][] memo; // 备忘录
public int minFlipsMonoIncr(String s) {
int n = s.length();
memo = new int[n + 1][2];
for(int[] arr : memo){
Arrays.fill(arr, -1);
}
return dp(s.toCharArray(), 0, 0);
} int dp(char[] s, int ind, int pre){ // sb是字符串,ind 是字符串当前位,pre 是前一位
if(ind == s.length){ // 到字符串结尾了,需要改变的字符为0,返回值为0
return 0;
}
if(memo[ind][pre] != -1){
return memo[ind][pre];
} int res = 0; if(s[ind] == '0'){ // 当前位为0
if(pre == 0){ // 前一位为 0
// 保持0不变;
// 把当前0变为1,翻转次数加一;
// 取两者中较小的情况
res = Math.min(dp(s, ind + 1, 0), dp(s, ind + 1, 1) + 1);
}else if(pre == 1){ // 前一位为 1
res = dp(s, ind + 1, 1) + 1; // 前一位为1,当前位为0,必须变成1
}
}else if(s[ind] == '1'){ // 当前位为1
if(pre == 0){ // 前一位为 0
// 保持1不变;
// 把当前1变为0,翻转次数加一;
// 取两者中较小的情况
res = Math.min(dp(s, ind + 1, 1), dp(s, ind + 1, 0) + 1);
}else if(pre == 1){
res = dp(s, ind + 1, 1); // 前一位为1,当前位为1,当前1必须保持不变
}
} memo[ind][pre] = res;
return res;
}
}

一开始写的时候有个问题,就是在递归函数的参数中记录结果,递归到边界的时候得到结果,这样就是一个纯递归的思路。并没有转成子问题的形式,因此我后续进行备忘录优化始终无法成功。原因还是递归函数定义有问题。

纯递归的代码

class Solution {
int[][][] memo;
public int minFlipsMonoIncr(String s) {
StringBuilder sb = new StringBuilder(s);
int n = s.length();
memo = new int[n + 1][2][2];
for(int[][] arr : memo){
for(int[] a : arr)
Arrays.fill(a, -1);
}
return dp(sb, 0, 0,0 ,sb.charAt(0) - '0');
} int dp(StringBuilder sb, int ind, int cnt, int pre, int now){
if(ind == sb.length()){
return cnt;
}
if(memo[ind][pre][now] != -1){
return memo[ind][pre][now];
} int res = 0; if(sb.charAt(ind) == '0'){
if(ind - 1 >= 0){
if(sb.charAt(ind - 1) == '0'){
int a = dp(sb, ind + 1, cnt, sb.charAt(ind - 1) - '0',0);
sb.setCharAt(ind, '1');
int b = dp(sb, ind + 1, cnt + 1, sb.charAt(ind - 1) - '0',1);
sb.setCharAt(ind, '0');
res += Math.min(a, b);
}else{
sb.setCharAt(ind, '1');
res += dp(sb, ind + 1, cnt + 1, sb.charAt(ind - 1) - '0',1);
sb.setCharAt(ind, '0');
}
}else{
int a = dp(sb, ind + 1, cnt, 0,0);
sb.setCharAt(ind, '1');
int b = dp(sb, ind + 1, cnt + 1, 0,1);
sb.setCharAt(ind, '0');
res += Math.min(a, b);
} }else{
if(ind - 1 >= 0){
if(sb.charAt(ind - 1) == '0'){
int a = dp(sb, ind + 1, cnt, sb.charAt(ind - 1) - '0',1);
sb.setCharAt(ind, '0');
int b = dp(sb, ind + 1, cnt + 1, sb.charAt(ind - 1) - '0',0);
sb.setCharAt(ind, '1');
res += Math.min(a, b);
}else{
res += dp(sb, ind + 1, cnt, sb.charAt(ind - 1) - '0',1);
}
}else{
int a = dp(sb, ind + 1, cnt, 0,1);
sb.setCharAt(ind, '0');
int b = dp(sb, ind + 1, cnt + 1, 0,0);
sb.setCharAt(ind, '1');
res += Math.min(a, b);
}
} memo[ind][pre][now] = res;
return res;
}
}

其中cnt就是最后的结果,这样可以通过数据量小的问题,但数据量大的问题必定会超时,而且无法利用记忆化搜索优化。

总结

不管是不是动态规划问题,首先写出递归的暴力解。如果超时,考虑有没有重叠子问题,此时就要注意递归函数的定义,递归函数的返回值应该是子问题的解。可能一开始结果保存在函数参数中是比较好想的。如果一开始写的递归函数是结果在函数参数里的形式,要考虑将结果定义在返回值中,此时需要明确递归函数的定义。

【力扣】剑指 Offer II 092. 翻转字符的更多相关文章

  1. 刷题-力扣-剑指 Offer II 055. 二叉搜索树迭代器

    剑指 Offer II 055. 二叉搜索树迭代器 题目链接 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/kTOapQ 著作权归领扣网络所有 ...

  2. 力扣 - 剑指 Offer 58 - I. 翻转单词顺序

    题目 剑指 Offer 58 - I. 翻转单词顺序 思路1 假如题目要求我们翻转字符串,那么我们可以从末尾往前开始遍历每一个字符,同时将每一个字符添加到临时空间,最后输出临时空间的数据就完成翻转了, ...

  3. 力扣 - 剑指 Offer 53 - II. 0~n-1中缺失的数字

    题目 剑指 Offer 53 - II. 0-n-1中缺失的数字 思路1 排序数组找数字使用二分法 通过题目,我们可以得到一个规律: 如果数组的索引值和该位置的值相等,说明还未缺失数字 一旦不相等了, ...

  4. 力扣 - 剑指 Offer 57 - II. 和为s的连续正数序列

    题目 剑指 Offer 57 - II. 和为s的连续正数序列 思路1(双指针/滑动窗口) 所谓滑动窗口,就是需要我们从一个序列中找到某些连续的子序列,我们可以使用两个for循环来遍历查找,但是未免效 ...

  5. 力扣 - 剑指 Offer 55 - II. 平衡二叉树

    题目 剑指 Offer 55 - II. 平衡二叉树 思路1(后序遍历+剪枝) 这题是上一题剑指 Offer 55 - I. 二叉树的深度的进阶,逻辑代码和那个一样,也是后续遍历,获取两个子节点较大的 ...

  6. 刷题-力扣-剑指 Offer 15. 二进制中1的个数

    剑指 Offer 15. 二进制中1的个数 题目链接 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/er-jin-zhi-zhong-1de- ...

  7. 刷题-力扣-剑指 Offer 42. 连续子数组的最大和

    剑指 Offer 42. 连续子数组的最大和 题目链接 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/lian-xu-zi-shu-zu-de ...

  8. 力扣 - 剑指 Offer 09. 用两个栈实现队列

    目录 题目 思路 代码 复杂度分析 题目 剑指 Offer 09. 用两个栈实现队列 思路 刚开始想的是用stack1作为数据存储的地方,stack2用来作为辅助栈,如果添加元素直接push入stac ...

  9. 力扣 - 剑指 Offer 37. 序列化二叉树

    目录 题目 思路 代码 复杂度分析 题目 剑指 Offer 37. 序列化二叉树 思路 序列化其实就是层序遍历 但是,要能反序列化的话,前.中.后.层序遍历是不够的,必须在序列化时候保存所有信息,这样 ...

随机推荐

  1. mybatisPlus在Springboot中的使用

    文章目录 1.简介 2.支持的数据库 3.框架 4.创建一个springboot项目 4.1 .pom文件中加入依赖 4.2.yml文件的配置 4.3 .数据库脚本 4.4.实体类 4.5 .启动类添 ...

  2. [WPF] 抄抄超强的苹果官网滚动文字特效实现

    1. 前言 今天 ChokCoco 大佬发布了一篇博客 超强的苹果官网滚动文字特效实现,iPhone 我是买不起的,但不妨碍我对抄特效感兴趣,正好我这周安排的工作已经完成了,于是有空练练手实现了一个 ...

  3. 中国制霸生成器「GitHub 热点速览 v.22.42」

    火遍推特的中国制霸生成器本周一开源就占据了两天的 GitHub Trending 榜,不知道你的足迹遍布了多少个省份呢?同样记录痕迹的 kanal 用了内存读写方式解决了 Rust 的消息处理问题,P ...

  4. kubelet忽然不可用

    原因,有可能机器的cpu信息有变化(扩容或者缩容)解决办法: 删掉/opt/var/lib/kubelet目录下(或者/data/lib/kubelet)cpu_manager_state文件 然后m ...

  5. 现在入行Java真的还有出路吗?

    现在入行Java还来的及吗?未来该如何抉择? 最近有点迷茫了,互联网好像一夜之间进入了寒冬一样,到处都是就业难的样子,当然,不可否认的是,那些努力的大佬依旧是斩获多个offer,但是,窝就一个小镇做题 ...

  6. 重新整理 .net core 实践篇 ———— linux上排查问题实用工具 [外篇]

    前言 介绍下面几个工具: Lldb createdump dotnet-dump dotnet-gcdump dotnet-symbol Procdump 该文的前置篇为: https://www.c ...

  7. vue引用MarkDown(mavonEditor)编辑器,文档

    mavonEditor Install mavon-editor (安装) npm install mavon-editor --save 如何引入: 全局引用: // 全局注册 import Vue ...

  8. PHP实现CURL发送请求

    public function curl($url, $params = false, $ispost = 0) { $httpInfo = array(); //初始化 $ch = curl_ini ...

  9. 4 c++编程-提高篇-STL简介

    ​ 重新系统学习c++语言,并将学习过程中的知识在这里抄录.总结.沉淀.同时希望对刷到的朋友有所帮助,一起加油哦!  生命就像一朵花,要拼尽全力绽放!死磕自个儿,身心愉悦! 写在前面,本篇章主要简单介 ...

  10. 安装BurpSuite (专业版)

    BurpSuite简介: Burp Suite 是用于攻击web 应用程序的集成平台.它包含了许多工具,并为这些工具设计了许多接口,以促进加快攻击应用程序的过程.所有的工具都共享一个能处理并显示HTT ...