【二维动态规划:交错字符串】
介绍
编程语言: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&…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...
SpringBoot-17-MyBatis动态SQL标签之常用标签
文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...
SciencePlots——绘制论文中的图片
文章目录 安装一、风格二、1 资源 安装 # 安装最新版 pip install githttps://github.com/garrettj403/SciencePlots.git# 安装稳定版 pip install SciencePlots一、风格 简单好用的深度学习论文绘图专用工具包–Science Plot 二、 1 资源 论文绘图神器来了:一行…...
vue3 字体颜色设置的多种方式
在Vue 3中设置字体颜色可以通过多种方式实现,这取决于你是想在组件内部直接设置,还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法: 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...
JDK 17 新特性
#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持,不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的ÿ…...
【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习
禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...
20个超级好用的 CSS 动画库
分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码,而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库,可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画,可以包含在你的网页或应用项目中。 3.An…...
Netty从入门到进阶(二)
二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架,用于…...
面试高频问题
文章目录 🚀 消息队列核心技术揭秘:从入门到秒杀面试官1️⃣ Kafka为何能"吞云吐雾"?性能背后的秘密1.1 顺序写入与零拷贝:性能的双引擎1.2 分区并行:数据的"八车道高速公路"1.3 页缓存与批量处理…...
实战设计模式之模板方法模式
概述 模板方法模式定义了一个操作中的算法骨架,并将某些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的前提下,重新定义算法中的某些步骤。简单来说,就是在一个方法中定义了要执行的步骤顺序或算法框架,但允许子类…...
