LeetCode(97):交错字符串
Hard!
题目描述:
给定三个字符串 s1, s2, s3, 验证 s3 是否是由 s1 和 s2 交错组成的。
示例 1:
输入: s1 = "aabcc", s2 = "dbbca", s3 = "aadbbcbcac"
输出: true
示例 2:
输入: s1 = "aabcc", s2 = "dbbca", s3 = "aadbbbaccc"
输出: false
解题思路:
这道求交织相错的字符串和之前那道Word Break 拆分词句 的题很类似,就像之前说的只要是遇到字符串的子序列或是匹配问题直接就上动态规划Dynamic Programming,其他的都不要考虑,什么递归呀的都是浮云,千辛万苦的写了递归结果拿到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 个字符,根据以上分析,可写出代码如下:
C++解法一:
class Solution {
public:
bool isInterleave(string s1, string s2, string s3) {
if (s1.size() + s2.size() != s3.size()) return false;
int n1 = s1.size();
int n2 = s2.size();
vector<vector<bool> > dp(n1 + 1, vector<bool> (n2 + 1, false));
dp[0][0] = true;
for (int i = 1; i <= n1; ++i) {
dp[i][0] = dp[i - 1][0] && (s1[i - 1] == s3[i - 1]);
}
for (int i = 1; i <= n2; ++i) {
dp[0][i] = dp[0][i - 1] && (s2[i - 1] == s3[i - 1]);
}
for (int i = 1; i <= n1; ++i) {
for (int j = 1; j <= n2; ++j) {
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]);
}
}
return dp[n1][n2];
}
};
我们也可以把for循环合并到一起,用if条件来处理边界情况,整体思路和上面的解法没有太大的区别。
C++解法二:
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来做,我们使用一个哈希集合,用来保存匹配失败的情况,我们分别用变量i,j,和k来记录字符串s1,s2,和s3匹配到的位置,初始化的时候都传入0。在递归函数中,首先根据i和j,算出key值,由于我们的哈希集合中只能放一个数字,而我们要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即可。
C++解法三:
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)位置,这里我们还要使用哈希集合,不过此时保存到是已经遍历过的位置,队列中还是存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。
C++解法四:
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;
}
};
LeetCode(97):交错字符串的更多相关文章
- Java实现 LeetCode 97 交错字符串
97. 交错字符串 给定三个字符串 s1, s2, s3, 验证 s3 是否是由 s1 和 s2 交错组成的. 示例 1: 输入: s1 = "aabcc", s2 = " ...
- [每日一题2020.06.09] leetcode #97 交错字符串 dp
题目链接 利用动态规划的思想, 对于每种状态(i, j)来说都有(i-1, j) 和 (i,j-1) 需要注意的问题 : 初始化的问题,先把i=0和j=0的状态都初始化后才可以进行dp否则发生数组越界 ...
- C#LeetCode刷题-字符串
字符串篇 # 题名 刷题 通过率 难度 3 无重复字符的最长子串 24.6% 中等 5 最长回文子串 22.4% 中等 6 Z字形变换 35.8% 中等 8 字符串转整数 (atoi) ...
- C#版(击败97.76%的提交) - Leetcode 557. 反转字符串中的单词 III - 题解
版权声明: 本文为博主Bravo Yeung(知乎UserName同名)的原创文章,欲转载请先私信获博主允许,转载时请附上网址 http://blog.csdn.net/lzuacm. Leetcod ...
- C#版(击败100.00%的提交) - Leetcode 151. 翻转字符串里的单词 - 题解
版权声明: 本文为博主Bravo Yeung(知乎UserName同名)的原创文章,欲转载请先私信获博主允许,转载时请附上网址 http://blog.csdn.net/lzuacm. C#版 - L ...
- LeetCode:反转字符串中的元音字母【345】
LeetCode:反转字符串中的元音字母[345] 题目描述 编写一个函数,以字符串作为输入,反转该字符串中的元音字母. 示例 1: 输入: "hello" 输出: "h ...
- LeetCode初级算法--字符串01:反转字符串
LeetCode初级算法--字符串01:反转字符串 搜索微信公众号:'AI-ming3526'或者'计算机视觉这件小事' 获取更多算法.机器学习干货 csdn:https://blog.csdn.ne ...
- 前端与算法 leetcode 344. 反转字符串
目录 # 前端与算法 leetcode 344. 反转字符串 题目描述 概要 提示 解析 解法一:双指针 解法二:递归 算法 传入测试用例的运行结果 执行结果 GitHub仓库 # 前端与算法 lee ...
- LeetCode初级算法--字符串02:字符串中的第一个唯一字符
LeetCode初级算法--字符串02:字符串中的第一个唯一字符 搜索微信公众号:'AI-ming3526'或者'计算机视觉这件小事' 获取更多算法.机器学习干货 csdn:https://blog. ...
- leetcode python反转字符串中的单词
# Leetcode 557 反转字符串中的单词III### 题目描述 给定一个字符串,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序. **示例1:** 输入: "L ...
随机推荐
- Viterbi
把语音分割为计算发音质量测度所需要的小单元时候,需要进行Viterbi对齐 Viterbi,在htk和sphinx中,也被称作token passing model Viterbi解码图是 状态数Sm ...
- 前端-----css(1)
css概述 CSS是Cascading Style Sheets的简称,中文称为层叠样式表,用来控制网页数据的表现,可以使网页的表现与数据内容分离. css知识点 注释 /* 注释内容 */ css四 ...
- vmware常用命令
控制台桌面执行快捷键ctrl+alt+f2 可以进入命令行
- TCP通信实现对接硬件发送与接收十六进制数据 & int与byte的转换原理 & java中正负数的表示
今天收到的一份需求任务是对接硬件,TCP通信,并给出通信端口与数据包格式,如下: 1.首先编写了一个简单的十六进制转byte[]数组与byte[]转换16进制字符串的两个方法,如下: /** * 将十 ...
- [转] Implementing a CNN for Text Classification in TensorFlow
Github上的一个开源项目,文档讲得极清晰 Github - https://github.com/dennybritz/cnn-text-classification-tf 原文- http:// ...
- ASP.NET Core中使用Autofac
⒈添加相关依赖 Install-Package Autofac ⒉扫描项目接口实现类 using Autofac; using System; using System.Collections.Gen ...
- canves绘制北京地铁线路图,包括线路绘制,优先路线,单路径选择。
canves绘制北京地铁线路图,包括线路绘制,优先路线,单路径选择. 即将推出,后台涵盖各种语言,php,C#,java,nodejs等.
- sublime修改侧边栏字体
安装了sublime的material主题后,侧边栏的字体特别小,设置方法如下: 安装插件 用上面的插件打开 Material-Theme.sublime-theme 如下 { "class ...
- python3+selenium入门05-元素操作及常用方法
学习了元素定位之后,来看一些元素的操作,还有一些常用的方法 clear()清空输入框内容 click()点击 send_keys()键盘输入 import time from selenium imp ...
- ThreadLocal与Synchronized区别
ThreadLocal和Synchonized都用于解决多线程并发访问他们两者的区别:synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问,而ThreadLocal为每一 ...