题目


解题思路

一个很暴力的想法,在满足单调递增的前提下,使每一位分别取 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. reportportal 集成 robotframework 自动化执行及结果可视化

    前言: 最近领导想了个需求,想把目前组内在linux平台上执行的自动化脚本搞成可视化,如果是web站点相关日志可视化倒是简单了,ELK就是不错的选择,大部分可视化项目这种的,可以做的开起来很炫. 我们 ...

  2. 豆瓣电影排行简单数据爬取_pyhton

    先安装一下requests和bs4库: cmd下面:python -m pip install bs4 -i https://pypi.douban.com/simple 代码: import req ...

  3. Hadoop集群简单入门

    Hadoop集群搭建 自己配置Hadoop的话太过复杂了,因为自己着急学习,就使用了黑马的快照.如果小伙伴们也想的话可以直接看黑马的课程,快照的话关注黑马程序员公众号,输入Hadoop就能获取资料,到 ...

  4. JavaScript基础复盘补缺

    语法规范 JavaScript严格区分大小写,对空格.换行.缩进不敏感,建议语句结束加':' JavaScript 会忽略多个空格.您可以向脚本添加空格,以增强可读性. JavaScript 程序员倾 ...

  5. calico和flannel的优缺点

    1.Kubernetes通信问题 1.容器间通信:即同一个Pod内多个容器间通信,通常使用loopback来实现. 2.Pod间通信:K8s要求,Pod和Pod之间通信必须使用Pod-IP 直接访问另 ...

  6. 基于LZO的高性能无损数据压缩IP

    LZOAccel-C LZO Data Compression Core/无损数据压缩IP Core LZOAccel-C是一个无损数据压缩引擎的FPGA硬件实现,兼容LZO 2.10标准. Core ...

  7. 研发效能|DevOps 已死平台工程永存带来的焦虑

    最近某位大神在推特上发了一个帖子,结果引来了国内众多卖课机构.培训机构的狂欢,开始贩卖焦虑,其实「平台工程」也不是什么特别高深莫测的东西.闲得无聊,把这位大神的几个帖子薅了下来,你看过之后就会觉得没啥 ...

  8. springMVC实现文件的上传和下载

    文件的下载功能 @RequestMapping("/testDown")public ResponseEntity<byte[]> testResponseEntity ...

  9. webscraper 无代码爬虫

    官网:https://www.webscraper.io/web-scraper-first-time-install webscraper 简介 Web Scraper 是一款免费的,适用于普通用户 ...

  10. <一>类,对象,this指针

    C++ 类:实体的抽象类型 实体(属性,行为) ->ADT(abstract data type) 类(属性->成员变量,行为->成员方法) OOP语言4大特征 抽象 封装/隐藏(通 ...