【力扣】剑指 Offer II 092. 翻转字符
题目


解题思路
一个很暴力的想法,在满足单调递增的前提下,使每一位分别取 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. 翻转字符的更多相关文章
- 刷题-力扣-剑指 Offer II 055. 二叉搜索树迭代器
剑指 Offer II 055. 二叉搜索树迭代器 题目链接 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/kTOapQ 著作权归领扣网络所有 ...
- 力扣 - 剑指 Offer 58 - I. 翻转单词顺序
题目 剑指 Offer 58 - I. 翻转单词顺序 思路1 假如题目要求我们翻转字符串,那么我们可以从末尾往前开始遍历每一个字符,同时将每一个字符添加到临时空间,最后输出临时空间的数据就完成翻转了, ...
- 力扣 - 剑指 Offer 53 - II. 0~n-1中缺失的数字
题目 剑指 Offer 53 - II. 0-n-1中缺失的数字 思路1 排序数组找数字使用二分法 通过题目,我们可以得到一个规律: 如果数组的索引值和该位置的值相等,说明还未缺失数字 一旦不相等了, ...
- 力扣 - 剑指 Offer 57 - II. 和为s的连续正数序列
题目 剑指 Offer 57 - II. 和为s的连续正数序列 思路1(双指针/滑动窗口) 所谓滑动窗口,就是需要我们从一个序列中找到某些连续的子序列,我们可以使用两个for循环来遍历查找,但是未免效 ...
- 力扣 - 剑指 Offer 55 - II. 平衡二叉树
题目 剑指 Offer 55 - II. 平衡二叉树 思路1(后序遍历+剪枝) 这题是上一题剑指 Offer 55 - I. 二叉树的深度的进阶,逻辑代码和那个一样,也是后续遍历,获取两个子节点较大的 ...
- 刷题-力扣-剑指 Offer 15. 二进制中1的个数
剑指 Offer 15. 二进制中1的个数 题目链接 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/er-jin-zhi-zhong-1de- ...
- 刷题-力扣-剑指 Offer 42. 连续子数组的最大和
剑指 Offer 42. 连续子数组的最大和 题目链接 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/lian-xu-zi-shu-zu-de ...
- 力扣 - 剑指 Offer 09. 用两个栈实现队列
目录 题目 思路 代码 复杂度分析 题目 剑指 Offer 09. 用两个栈实现队列 思路 刚开始想的是用stack1作为数据存储的地方,stack2用来作为辅助栈,如果添加元素直接push入stac ...
- 力扣 - 剑指 Offer 37. 序列化二叉树
目录 题目 思路 代码 复杂度分析 题目 剑指 Offer 37. 序列化二叉树 思路 序列化其实就是层序遍历 但是,要能反序列化的话,前.中.后.层序遍历是不够的,必须在序列化时候保存所有信息,这样 ...
随机推荐
- mybatisPlus在Springboot中的使用
文章目录 1.简介 2.支持的数据库 3.框架 4.创建一个springboot项目 4.1 .pom文件中加入依赖 4.2.yml文件的配置 4.3 .数据库脚本 4.4.实体类 4.5 .启动类添 ...
- [WPF] 抄抄超强的苹果官网滚动文字特效实现
1. 前言 今天 ChokCoco 大佬发布了一篇博客 超强的苹果官网滚动文字特效实现,iPhone 我是买不起的,但不妨碍我对抄特效感兴趣,正好我这周安排的工作已经完成了,于是有空练练手实现了一个 ...
- 中国制霸生成器「GitHub 热点速览 v.22.42」
火遍推特的中国制霸生成器本周一开源就占据了两天的 GitHub Trending 榜,不知道你的足迹遍布了多少个省份呢?同样记录痕迹的 kanal 用了内存读写方式解决了 Rust 的消息处理问题,P ...
- kubelet忽然不可用
原因,有可能机器的cpu信息有变化(扩容或者缩容)解决办法: 删掉/opt/var/lib/kubelet目录下(或者/data/lib/kubelet)cpu_manager_state文件 然后m ...
- 现在入行Java真的还有出路吗?
现在入行Java还来的及吗?未来该如何抉择? 最近有点迷茫了,互联网好像一夜之间进入了寒冬一样,到处都是就业难的样子,当然,不可否认的是,那些努力的大佬依旧是斩获多个offer,但是,窝就一个小镇做题 ...
- 重新整理 .net core 实践篇 ———— linux上排查问题实用工具 [外篇]
前言 介绍下面几个工具: Lldb createdump dotnet-dump dotnet-gcdump dotnet-symbol Procdump 该文的前置篇为: https://www.c ...
- vue引用MarkDown(mavonEditor)编辑器,文档
mavonEditor Install mavon-editor (安装) npm install mavon-editor --save 如何引入: 全局引用: // 全局注册 import Vue ...
- PHP实现CURL发送请求
public function curl($url, $params = false, $ispost = 0) { $httpInfo = array(); //初始化 $ch = curl_ini ...
- 4 c++编程-提高篇-STL简介
重新系统学习c++语言,并将学习过程中的知识在这里抄录.总结.沉淀.同时希望对刷到的朋友有所帮助,一起加油哦! 生命就像一朵花,要拼尽全力绽放!死磕自个儿,身心愉悦! 写在前面,本篇章主要简单介 ...
- 安装BurpSuite (专业版)
BurpSuite简介: Burp Suite 是用于攻击web 应用程序的集成平台.它包含了许多工具,并为这些工具设计了许多接口,以促进加快攻击应用程序的过程.所有的工具都共享一个能处理并显示HTT ...