[LeetCode] Interleaving String 交织相错的字符串
Given s1, s2, s3, find whether s3 is formed by the interleaving of s1 and s2.
Example 1:
Input: s1 = "aabcc", s2 = "dbbca", s3 = "aadbbcbcac"
Output: true
Example 2:
Input: s1 = "aabcc", s2 = "dbbca", s3 = "aadbbbaccc"
Output: false
这道求交织相错的字符串和之前那道 Word Break 的题很类似,就像我之前说的只要是遇到字符串的子序列或是匹配问题直接就上动态规划 Dynamic Programming,其他的都不要考虑,什么递归呀的都是浮云(当然带记忆数组的递归写法除外,因为这也可以算是 DP 的一种),千辛万苦的写了递归结果拿到 OJ 上妥妥 Time Limit Exceeded,能把人气昏了,所以还是直接就考虑 DP 解法省事些。一般来说字符串匹配问题都是更新一个二维 dp 数组,核心就在于找出状态转移方程。那么我们还是从题目中给的例子出发吧,手动写出二维数组 dp 如下:
Ø d b b c a
Ø T F F F F F
a T F F F F F
a T T T T T F
b F T T F T F
c F F T T T T
c F F F T F T
首先,这道题的大前提是字符串 s1 和 s2 的长度和必须等于 s3 的长度,如果不等于,肯定返回 false。那么当 s1 和 s2 是空串的时候,s3 必然是空串,则返回 true。所以直接给 dp[0][0] 赋值 true,然后若 s1 和 s2 其中的一个为空串的话,那么另一个肯定和 s3 的长度相等,则按位比较,若相同且上一个位置为 True,赋 True,其余情况都赋 False,这样的二维数组 dp 的边缘就初始化好了。下面只需要找出状态转移方程来更新整个数组即可,我们发现,在任意非边缘位置 dp[i][j] 时,它的左边或上边有可能为 True 或是 False,两边都可以更新过来,只要有一条路通着,那么这个点就可以为 True。那么我们得分别来看,如果左边的为 True,那么我们去除当前对应的 s2 中的字符串 s2[j - 1] 和 s3 中对应的位置的字符相比(计算对应位置时还要考虑已匹配的s1中的字符),为 s3[j - 1 + i], 如果相等,则赋 True,反之赋 False。 而上边为 True 的情况也类似,所以可以求出状态转移方程为:
dp[i][j] = (dp[i - 1][j] && s1[i - 1] == s3[i - 1 + j]) || (dp[i][j - 1] && s2[j - 1] == s3[j - 1 + i]);
其中 dp[i][j] 表示的是 s2 的前 i 个字符和 s1 的前 j 个字符是否匹配 s3 的前 i+j 个字符,根据以上分析,可写出代码如下:
解法一:
class Solution {
public:
bool isInterleave(string s1, string s2, string s3) {
if (s1.size() + s2.size() != s3.size()) return false;
int n1 = s1.size(), n2 = s2.size();
vector<vector<bool>> dp(n1 + , vector<bool> (n2 + ));
dp[][] = true;
for (int i = ; i <= n1; ++i) {
dp[i][] = dp[i - ][] && (s1[i - ] == s3[i - ]);
}
for (int i = ; i <= n2; ++i) {
dp[][i] = dp[][i - ] && (s2[i - ] == s3[i - ]);
}
for (int i = ; i <= n1; ++i) {
for (int j = ; j <= n2; ++j) {
dp[i][j] = (dp[i - ][j] && s1[i - ] == s3[i - + j]) || (dp[i][j - ] && s2[j - ] == s3[j - + i]);
}
}
return dp[n1][n2];
}
};
我们也可以把for循环合并到一起,用if条件来处理边界情况,整体思路和上面的解法没有太大的区别,参见代码如下:
解法二:
class Solution {
public:
bool isInterleave(string s1, string s2, string s3) {
if (s1.size() + s2.size() != s3.size()) return false;
int n1 = s1.size(), n2 = s2.size();
vector<vector<bool>> dp(n1 + , vector<bool> (n2 + , false));
for (int i = ; i <= n1; ++i) {
for (int j = ; j <= n2; ++j) {
if (i == && j == ) {
dp[i][j] = true;
} else if (i == ) {
dp[i][j] = dp[i][j - ] && s2[j - ] == s3[i + j - ];
} else if (j == ) {
dp[i][j] = dp[i - ][j] && s1[i - ] == s3[i + j - ];
} else {
dp[i][j] = (dp[i - ][j] && s1[i - ] == s3[i + j - ]) || (dp[i][j - ] && s2[j - ] == s3[i + j - ]);
}
}
}
return dp[n1][n2];
}
};
这道题也可以使用带优化的 DFS 来做,我们使用一个 HashSet,用来保存匹配失败的情况,我们分别用变量i,j,和k来记录字符串 s1,s2,和 s3 匹配到的位置,初始化的时候都传入0。在递归函数中,首先根据i和j,算出 key 值,由于我们的 HashSet 中只能放一个数字,而我们要 encode 两个数字i和j,所以通过用i乘以 s3 的长度再加上j来得到 key,此时我们看,如果 key 已经在集合中,直接返回 false,因为集合中存的是无法匹配的情况。然后先来处理 corner case 的情况,如果i等于 s1 的长度了,说明 s1 的字符都匹配完了,此时 s2 剩下的字符和 s3 剩下的字符可以直接进行匹配了,所以我们直接返回两者是否能匹配的 bool 值。同理,如果j等于 s2 的长度了,说明 s2 的字符都匹配完了,此时 s1 剩下的字符和 s3 剩下的字符可以直接进行匹配了,所以我们直接返回两者是否能匹配的 bool 值。如果 s1 和 s2 都有剩余字符,那么当 s1 的当前字符等于 s3 的当前字符,那么调用递归函数,注意i和k都加上1,如果递归函数返回 true,则当前函数也返回 true;还有一种情况是,当 s2 的当前字符等于 s3 的当前字符,那么调用递归函数,注意j和k都加上1,如果递归函数返回 true,那么当前函数也返回 true。如果匹配失败了,则将 key 加入集合中,并返回 false 即可,参见代码如下:
解法三:
class Solution {
public:
bool isInterleave(string s1, string s2, string s3) {
if (s1.size() + s2.size() != s3.size()) return false;
unordered_set<int> s;
return helper(s1, , s2, , s3, , s);
}
bool helper(string& s1, int i, string& s2, int j, string& s3, int k, unordered_set<int>& s) {
int key = i * s3.size() + j;
if (s.count(key)) return false;
if (i == s1.size()) return s2.substr(j) == s3.substr(k);
if (j == s2.size()) return s1.substr(i) == s3.substr(k);
if ((s1[i] == s3[k] && helper(s1, i + , s2, j, s3, k + , s)) ||
(s2[j] == s3[k] && helper(s1, i, s2, j + , s3, k + , s))) return true;
s.insert(key);
return false;
}
};
既然 DFS 可以,那么 BFS 也就坐不住了,也要出来浪一波。这里我们需要用队列 queue 来辅助运算,如果将解法一讲解中的那个二维 dp 数组列出来的 TF 图当作一个迷宫的话,那么 BFS 的目的就是要从 (0, 0) 位置找一条都是T的路径通到 (n1, n2) 位置,这里我们还要使用 HashSet,不过此时保存到是已经遍历过的位置,队列中还是存 key 值,key 值的 encode 方法跟上面 DFS 解法的相同,初始时放个0进去。然后我们进行 while 循环,循环条件除了q不为空,还有一个是k小于 n3,因为匹配完 s3 中所有的字符就结束了。然后由于是一层层的遍历,所以要直接循环 queue 中元素个数的次数,在 for 循环中,对队首元素进行解码,得到i和j值,如果i小于 n1,说明 s1 还有剩余字符,如果 s1 当前字符等于 s3 当前字符,那么把 s1 的下一个位置 i+1 跟j一起加码算出 key 值,如果该 key 值不在于集合中,则加入集合,同时加入队列 queue 中;同理,如果j小于 n2,说明 s2 还有剩余字符,如果 s2 当前字符等于 s3 当前字符,那么把 s2 的下一个位置 j+1 跟i一起加码算出 key 值,如果该 key 值不在于集合中,则加入集合,同时加入队列 queue 中。for 循环结束后,k自增1。最后如果匹配成功的话,那么 queue 中应该只有一个 (n1, n2) 的 key 值,且k此时等于 n3,所以当 queue 为空或者k不等于 n3 的时候都要返回 false,参见代码如下:
解法四:
class Solution {
public:
bool isInterleave(string s1, string s2, string s3) {
if (s1.size() + s2.size() != s3.size()) return false;
int n1 = s1.size(), n2 = s2.size(), n3 = s3.size(), k = ;
unordered_set<int> s;
queue<int> q{{}};
while (!q.empty() && k < n3) {
int len = q.size();
for (int t = ; t < len; ++t) {
int i = q.front() / n3, j = q.front() % n3; q.pop();
if (i < n1 && s1[i] == s3[k]) {
int key = (i + ) * n3 + j;
if (!s.count(key)) {
s.insert(key);
q.push(key);
}
}
if (j < n2 && s2[j] == s3[k]) {
int key = i * n3 + j + ;
if (!s.count(key)) {
s.insert(key);
q.push(key);
}
}
}
++k;
}
return !q.empty() && k == n3;
}
};
参考资料:
https://leetcode.com/problems/interleaving-string/
https://discuss.leetcode.com/topic/7728/dp-solution-in-java
https://discuss.leetcode.com/topic/3532/my-dp-solution-in-c
https://discuss.leetcode.com/topic/30127/summary-of-solutions-bfs-dfs-dp
https://discuss.leetcode.com/topic/3436/my-accepted-java-recursive-solution-for-interleaving-string
LeetCode All in One 题目讲解汇总(持续更新中...)
[LeetCode] Interleaving String 交织相错的字符串的更多相关文章
- [LeetCode] 97. Interleaving String 交织相错的字符串
Given s1, s2, s3, find whether s3 is formed by the interleaving of s1and s2. Example 1: Input: s1 = ...
- [LeetCode] Interleaving String - 交织的字符串
题目如下:https://oj.leetcode.com/problems/interleaving-string/ Given s1, s2, s3, find whether s3 is form ...
- [leetcode]97. Interleaving String能否构成交错字符串
Given s1, s2, s3, find whether s3 is formed by the interleaving of s1 and s2. Input: s1 = "aabc ...
- Leetcode:Interleaving String 解题报告
Interleaving StringGiven s1, s2, s3, find whether s3 is formed by the interleaving of s1 and s2. For ...
- Leetcode 8 String to Integer (atoi) 字符串处理
题意:将字符串转化成数字. 前置有空格,同时有正负号,数字有可能会溢出,这里用long long解决(leetcode用的是g++编译器),这题还是很有难度的. class Solution { pu ...
- [LeetCode] Interleaving String 解题思路
Given s1, s2, s3, find whether s3 is formed by the interleaving of s1 and s2. For example,Given:s1 = ...
- [LeetCode] Interleaving String [30]
题目 Given s1, s2, s3, find whether s3 is formed by the interleaving of s1 and s2. For example, Given: ...
- [LeetCode] Backspace String Compare 退格字符串比较
Given two strings S and T, return if they are equal when both are typed into empty text editors. # m ...
- 【LeetCode】String to Integer (atoi)(字符串转换整数 (atoi))
这道题是LeetCode里的第8道题. 题目要求: 请你来实现一个 atoi 函数,使其能将字符串转换成整数. 首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止. 当我们 ...
随机推荐
- 探究负边距(negative margin)原理
W3C规范在介绍margin时有这样一句话: Negative values for margin properties are allowed, but there may be implement ...
- WebApi安全性 使用TOKEN+签名验证
首先问大家一个问题,你在写开放的API接口时是如何保证数据的安全性的?先来看看有哪些安全性问题在开放的api接口中,我们通过http Post或者Get方式请求服务器的时候,会面临着许多的安全性问题, ...
- [Oracle] Bulk Insert Data
命名空间:Oracle.DataAccess.Client 组件:Oracle.DataAccess.dll(2.112.1.0) ODP.NET 版本:ODP.NET for .NET Framew ...
- spring的依赖注入,为什么用接口的实现类而不是父类的继承类?
@Resource private EmployeeService employeeService; public void setEmployeeService(EmployeeService em ...
- JS高程5.引用类型(1)Object类型
引用类型 在ECMASCript中,引用类型是一种数据结构,将数据和功能组织在一起,引用类型有时候也被称为对象定义,因为它们描述的是一类对象所具有的属性和方法.(注意:尽管ECMAScript从技术上 ...
- node-sass报错解决方法
在Vue.js中,每一个vue文件都是一个组件,在.vue文件中可以将模板,脚本,样式写在一起,便于组织整个组件.在使用template,script时,编写css样式时,都进行的特别顺利,唯独当我想 ...
- arcgis api for js入门开发系列三地图工具栏(含源代码)
上一篇实现了demo的地图加载展示,在上篇实现的基础上,新增了地图工具栏以及通用地图控件功能,比如地图框选缩放.地图漫游.清空.量算工具.地图导航控件.地图比例尺控件.地图鹰眼图等等,总共分为5个部分 ...
- SQLSERVER常见系统函数之字符串函数(一)
好久没有写博客了,这段时间准备写一下字符串函数 QQ群: 499092562:欢迎交流 字符串函数: 1.LEN(需要获取长度的字符串) 返回:字符串的长度 示例: SELECT LEN('小搬运工很 ...
- Android笔记——Android自定义控件
目录: 1.自定义控件概述 01_什么是自定义控件 Android系统中,继承Android系统自带的View或者ViewGroup控件或者系统自带的控件,并在这基础上增加或者重新组合成我们想要的效果 ...
- Java连接数据库的辣几句话
Java连接数据库的辣几句话 1.java连接Oracle数据库 使用以下代码三个步骤: 1.下载ojdbc.jar包并导入项目中.附下载地址:http://download.csdn.net/det ...