【二维动态规划:交错字符串】
介绍
编程语言:Java
本篇介绍一道比较经典的二维动态规划题。
交错字符串
主要说明几点:
- 为什么双指针解不了?
- 为什么是二维动态规划?
- 根据题意分析处转移方程。
- 严格位置依赖和空间压缩优化。
题目介绍
题意有点抽象, 看一下示例吧。
第一种理解方式就是保持两个字符串相对顺序不变, 将s2切割成一个一个子串插入到s1中, 看能否构成字符串s3。
第二种理解方式是字符串s3是根据s1,s2保持相对顺序字符选择的结果。
因为保持各个字符串之间相对顺序不变, 那么可以尝试双指针的思路
比如如上图, s3的第一个和第二个字符只能来自s1, 第3个字符只能来自s2, 第4个字符有讲究了, 它既可能来自s1也可能来自s2, 那么到底来自哪儿, 都有可能!
下面分析这种双指针为什么是错误的。
双指针的错误解法
部分朋友看到题,相对次序不变, 很容易想到归并排序的merge部分, 自然而然的写出了, 如下代码。
class Solution {public boolean isInterleave(String str1, String str2, String str3) {char[] s1 = str1.toCharArray();char[] s2 = str2.toCharArray();char[] s3 = str3.toCharArray();if(s1.length + s2.length != s3.length){return false;}int p1 = 0, p2 = 0;int i = 0;while(p1<s1.length&&p2<s2.length){if(s1[p1]==s3[i]){p1++;i++;}else if(s2[p2]==s3[i]){p2++;i++;}else{return false;}}while(p1<s1.length){if(s1[p1++]!=s3[i++]){return false;}}while(p2<s2.length){if(s2[p2++]!=s3[i++]){return false;}}return true;}
}
大部分测试用例能过, 说明双指针的思想大概率是对的, 但是对于某些情况误判了。
误判了什么?
即字符串str1和str2的异步双指针, 在某些情况会出现指向相同字符的情况。
正如下图, 此刻双指针只能固定的选择一种方案, 要么选择str1匹配, 要么选择str2匹配。 实际最终是否能交错处str3, 取决于两种方案的并集。
双指针显然错过了一种方案, 况且这次选择后面可能还要遇见相同字符的分类方案。
因此, 这道题是动态分析, 应该动态规划分类讨论处理结果。
方法1:递归入手改写记忆化搜索
双指针没有处理分类讨论, 那么通过递归分情况讨论不就好了。
直接定义一个函数f(n)
, f(s1, p1, s2, p2, s3, p3)
:s1,s2表示原始的字符数组, s3表示交错出的字符数组。 p1,p2,p3分别表示当前指向各自字符数组的下标。
base case
是p3==s3.length, 即字符数组s3被s1,s2各自分配的字符匹配完了。
在保证字符数组不越界的情况, 如果s1,s2当前的字符同时匹配?
通过或逻辑就成功分类讨论了。 取结果的并集。
下面解法会超时, 但已经能过部分样例。
子问题重叠。
class Solution {public boolean isInterleave(String str1, String str2, String str3) {char[] s1 = str1.toCharArray();char[] s2 = str2.toCharArray();char[] s3 = str3.toCharArray();if(s1.length + s2.length != s3.length){return false;}return f(s1,0,s2,0,s3,0);}public static boolean f(char[] s1,int p1, char[]s2, int p2, char[] s3, int p3){if(p3 == s3.length){return true;}//选择方案boolean ans = false;if(p1 < s1.length && s1[p1] == s3[p3]){ans |= f(s1,p1+1,s2,p2,s3,p3+1);}if(p2 < s2.length && s2[p2] == s3[p3]){ans |= f(s1,p1,s2,p2+1,s3,p3+1);}return ans;}
}
接下用dp表优化
挂个bool dp表, 但是需要注意Java中boolean数组元素默认值是false.
false无法区分这个元素是否被处理过还是这个方案不行。
因此要改进成Boolean数组
, 用null
区分即可, 处理过的结果要么是false
或者true
。
为什么下面不是改进成三维动态规划?
因为最终情况要么能达到s3.length或者不能, 可以直接确定答案, 因此给dp表再加一维没有必要, 二维dp表就可以完美解决了.
下面代码提交可以打败100%。
class Solution {public boolean isInterleave(String str1, String str2, String str3) {char[] s1 = str1.toCharArray();char[] s2 = str2.toCharArray();char[] s3 = str3.toCharArray();if (s1.length + s2.length != s3.length) {return false;}// 创建dp和visited数组Boolean[][] dp = new Boolean[s1.length + 1][s2.length + 1];return f(s1, 0, s2, 0, s3, 0, dp);}private boolean f(char[] s1, int p1, char[] s2, int p2, char[] s3, int p3, Boolean[][] dp) {if (p3 == s3.length) { // 如果s3遍历完,返回truereturn true;}if (dp[p1][p2] != null) { // 如果已经计算过,直接返回结果return dp[p1][p2];}boolean ans = false;// 尝试从s1取字符匹配if (p1 < s1.length && s1[p1] == s3[p3]) {ans = f(s1, p1 + 1, s2, p2, s3, p3 + 1, dp);}// 如果从s1失败,尝试从s2取字符匹配if (!ans && p2 < s2.length && s2[p2] == s3[p3]) {ans = f(s1, p1, s2, p2 + 1, s3, p3 + 1, dp);}// 记录状态dp[p1][p2] = Boolean.valueOf(ans);return ans;}
}
方法2:直接入手转移方程
dp[i][j]
, 这里严格定义dp表的含义, 取字符数组s1的前i个, 另取字符数组的前j个, 能否组成s3数组的前i+j个。
s1的[0…i-1]区间, s2的[0…j-1]区间, 能否组成s3[0…i+j-1]的区间。
- 如果s1[i-1]==s3[i+j-1], 那么结果依赖dp[i-1][j]。
- 如果s2[j-1]==s3[i+j-1], 那么结果依赖dp[i][j-1]。
- 如果都不满足, 那么false, 不能交错组成。1,2情况任意一项成立, 那么结果为true。
dp表的简单部分如何填?
思考dp表的定义。
dp[0][0] = = true
, 显然。 根据前面的定义, 取字符数组s1的0个, 另取字符数组的0个, 能否组成s3数组的0个。必然可以。
//单独取一个字符数组进行匹配s3前面几个字符for(int i=1;i<=n;i++){if(s1[i-1] != s3[i-1]){break;}dp[i][0] = true;}for(int j=1;j<=m;j++){if(s2[j-1] != s3[j-1]){break;}dp[0][j] = true;}
class Solution {public boolean isInterleave(String str1, String str2, String str3) {if(str1.length() + str2.length() != str3.length() ){return false;}char[] s1 = str1.toCharArray();char[] s2 = str2.toCharArray();char[] s3 = str3.toCharArray();int n = s1.length;int m = s2.length;boolean[][] dp = new boolean[n+1][m+1];dp[0][0] = true;for(int i=1;i<=n;i++){if(s1[i-1] != s3[i-1]){break;}dp[i][0] = true;}for(int j=1;j<=m;j++){if(s2[j-1] != s3[j-1]){break;}dp[0][j] = true;}for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){dp[i][j] = (s1[i-1]==s3[i+j-1]&&dp[i-1][j])||(s2[j-1]==s3[i+j-1]&&dp[i][j-1]);}}return dp[n][m];}
}
方法3:空间压缩+位置依赖
由于每一行都跟上一行的状态有关, 因此仅需一个一维数组和少数变量就可以滚动更新, 减少内存使用。
从第0行开始自上向下自左向右进行滚动, 压缩成一维dp表。
class Solution {public boolean isInterleave(String str1, String str2, String str3) {if (str1.length() + str2.length() != str3.length()) {return false;}char[] s1 = str1.toCharArray();char[] s2 = str2.toCharArray();char[] s3 = str3.toCharArray();int n = s1.length;int m = s2.length;boolean[] dp = new boolean[m + 1];// 初始化第一列的状态,表示 s1 完全匹配 s3 的情况dp[0] = true;for (int j = 1; j <= m; j++) {if (s2[j - 1] != s3[j - 1]) {break; // 如果 s2[j-1] 和 s3[j-1] 不相等,后续就不需要继续检查了}dp[j] = true; // 初始化 dp[j] 的状态}// 处理 s1 和 s2 的交错匹配for (int i = 1; i <= n; i++) {dp[0] = (s1[i - 1] == s3[i - 1]) && dp[0]; // 更新 dp[0] 状态for (int j = 1; j <= m; j++) {dp[j] = (s1[i - 1] == s3[i + j - 1] && dp[j]) || (s2[j - 1] == s3[i + j - 1] && dp[j - 1]);}}return dp[m];}
}
结语
动态规划的题解好难写…😰😰😰.
空间压缩不会二维dp表真不好分析。
相关文章:

【二维动态规划:交错字符串】
介绍 编程语言:Java 本篇介绍一道比较经典的二维动态规划题。 交错字符串 主要说明几点: 为什么双指针解不了?为什么是二维动态规划?根据题意分析处转移方程。严格位置依赖和空间压缩优化。 题目介绍 题意有点抽象,…...
goframe开发一个企业网站 MongoDB 完整工具包18
1. MongoDB 工具包完整实现 (mongodb.go) package mongodbimport ("context""fmt""time""github.com/gogf/gf/v2/frame/g""go.mongodb.org/mongo-driver/mongo""go.mongodb.org/mongo-driver/mongo/options" )va…...
在vue中,根据后端接口返回的文件流实现word文件弹窗预览
需求 弹窗预览word文件,因浏览器无法直接根据blob路径直接预览word文件,所以需要利用插件实现。 解决方案 利用docx-preview实现word文件弹窗预览,以node版本16.21.3和docx-preview版本0.1.8为例 具体实现步骤 1、安装docx-preview插件 …...

动态规划之背包问题
0/1背包问题 1.二维数组解法 题目描述:有一个容量为m的背包,还有n个物品,他们的重量分别为w1、w2、w3.....wn,他们的价值分别为v1、v2、v3......vn。每个物品只能使用一次,求可以放进背包物品的最大价值。 输入样例…...
【Python】 深入理解Python的单元测试:用unittest和pytest进行测试驱动开发
《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 单元测试是现代软件开发中的重要组成部分,通过验证代码的功能性、准确性和稳定性,提升代码质量和开发效率。本文章深入介绍Python中两种主流单元测试框架:unittest和pytest,并结合测试驱动开发(TDD)…...
Java集合1.0
1.什么是集合? 集合就是一个存放数据的容器,准确的说是放数据对象引用的容器。 集合和数组的区别 数组是固定长度,集合是可变长度。数组可以存储基本数据类型,也可以存储引用数据类型,集合只能存储引用数据类型&…...

Leetcode 336 回文对
示例 1: 输入:words ["abcd","dcba","lls","s","sssll"] 输出:[[0,1],[1,0],[3,2],[2,4]] 解释:可拼接成的回文串为 ["dcbaabcd","abcddcba","sl…...

实现一个可配置的TCP设备模拟器,支持交互和解析配置
前言 诸位在做IOT开发的时候是否有遇到一个问题,那就是模拟一个设备来联调测试,虽然说现在的物联网通信主要是用mqtt通信,但还是有很多设备使用TCP这种协议交互,例如充电桩,还有一些工业设备,TCP这类报文交…...

算法的空间复杂度
空间复杂度 空间复杂度主要是衡量一个算法运行所需要的额外空间,在计算机发展早期,计算机的储存容量很小,所以空间复杂度是很重要的。但是经过计算机行业的迅速发展,计算机的容量已经不再是问题了,所以如今已经不再需…...

自定义协议
1. 问题引入 问题:TCP是面向字节流的(TCP不关心发送的数据是消息、文件还是其他任何类型的数据。它简单地将所有数据视为一个字节序列,即字节流。这意味着TCP不会对发送的数据进行任何特定的边界划分,它只是确保数据的顺序和完整…...

在 Taro 中实现系统主题适配:亮/暗模式
目录 背景实现方案方案一:CSS 变量 prefers-color-scheme 媒体查询什么是 prefers-color-scheme?代码示例 方案二:通过 JavaScript 监听系统主题切换 背景 用Taro开发的微信小程序,需求是页面的UI主题想要跟随手机系统的主题适配…...

autogen框架中使用chatglm4模型实现react
本文将介绍如何使用使用chatglm4实现react,利用环境变量、Tavily API和ReAct代理模式来回答用户提出的问题。 环境变量 首先,我们需要加载环境变量。这可以通过使用dotenv库来实现。 from dotenv import load_dotenv_ load_dotenv()注意.env文件处于…...
读《Effective Java》笔记 - 条目9
条目9:与try-finally 相比,首选 try -with -resource 什么是 try-finally? try-finally 是 Java 中传统的资源管理方式,通常用于确保资源(如文件流、数据库连接等)被正确关闭。 BufferedReader reader n…...

【软件入门】Git快速入门
Git快速入门 文章目录 Git快速入门0.前言1.安装和配置2.新建版本库2.1.本地创建2.2.云端下载 3.版本管理3.1.添加和提交文件3.2.回退版本3.2.1.soft模式3.2.2.mixed模式3.2.3.hard模式3.2.4.使用场景 3.3.查看版本差异3.4.忽略文件 4.云端配置4.1.Github4.1.1.SSH配置4.1.2.关联…...
nextjs window is not defined
问题产生的原因 在 Next.js 中,“window is not defined” 错误通常出现在服务器端渲染(Server - Side Rendering,SSR)的代码中。这是因为window对象是浏览器环境中的全局对象,在服务器端没有window这个概念。例如&am…...
C语言实现冒泡排序:从基础到优化全解析
一、什么是冒泡排序? 冒泡排序(Bubble Sort)是一种经典的排序算法,其工作原理非常直观:通过多次比较和交换相邻元素,将较大的元素“冒泡”到数组的末尾。经过多轮迭代,整个数组会变得有序。 二…...

windows11下git与 openssl要注意的问题
看了一下自己贴文的历史,有一条重要的忘了写了。 当时帮有位同事配置gitlab,众说周知gitlab是不太好操作。 但我还是自认自己git还是相当熟的。 解决了一系列问题,如配置代理,sshkey,私有库,等等࿰…...

lua除法bug
故事背景,新来了一个数值,要改公式。神奇的一幕出现了,公式算出一个非常大的数。排查是lua有一个除法bug,1除以大数得到一个非常大的数。 function div(a, b)return tonumber(string.format("%.2f", a/b)) end print(1/73003) pri…...

Ubuntu下Docker容器java服务往mysql插入中文数据乱码
一、问题描述 1、java服务部署在ubuntu下的docker容器内,但是会出现部分插入中文数据显示乱码,如图所示: 二、解决方案 1、查看mysql是否支持utf8,登录进入Mysql 输入命令: mysql -u root -pshow variables like c…...

C语言根据字符串变量获取/设置结构体成员值
一、背景 在项目中需要根据从数据库中获取的字段与对应的键值付给对应结构体成员上,而c语言中没有类似的反射机制,所以需要实现类似功能。例,从表中查到a 10,在结构体t中,需要将 t.a 10。 二、实现 感谢ChatGPT&…...

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...
ubuntu搭建nfs服务centos挂载访问
在Ubuntu上设置NFS服务器 在Ubuntu上,你可以使用apt包管理器来安装NFS服务器。打开终端并运行: sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享,例如/shared: sudo mkdir /shared sud…...

centos 7 部署awstats 网站访问检测
一、基础环境准备(两种安装方式都要做) bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats࿰…...
Java - Mysql数据类型对应
Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...
多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验
一、多模态商品数据接口的技术架构 (一)多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如,当用户上传一张“蓝色连衣裙”的图片时,接口可自动提取图像中的颜色(RGB值&…...

如何在看板中有效管理突发紧急任务
在看板中有效管理突发紧急任务需要:设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP(Work-in-Progress)弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中,设立专门的紧急任务通道尤为重要,这能…...
Nginx server_name 配置说明
Nginx 是一个高性能的反向代理和负载均衡服务器,其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机(Virtual Host)。 1. 简介 Nginx 使用 server_name 指令来确定…...
sqlserver 根据指定字符 解析拼接字符串
DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...

【配置 YOLOX 用于按目录分类的图片数据集】
现在的图标点选越来越多,如何一步解决,采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集(每个目录代表一个类别,目录下是该类别的所有图片),你需要进行以下配置步骤&#x…...
MySQL用户和授权
开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务: test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...