Given s1s2s3, 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 交织相错的字符串的更多相关文章

  1. [LeetCode] 97. Interleaving String 交织相错的字符串

    Given s1, s2, s3, find whether s3 is formed by the interleaving of s1and s2. Example 1: Input: s1 = ...

  2. [LeetCode] Interleaving String - 交织的字符串

    题目如下:https://oj.leetcode.com/problems/interleaving-string/ Given s1, s2, s3, find whether s3 is form ...

  3. [leetcode]97. Interleaving String能否构成交错字符串

    Given s1, s2, s3, find whether s3 is formed by the interleaving of s1 and s2. Input: s1 = "aabc ...

  4. Leetcode:Interleaving String 解题报告

    Interleaving StringGiven s1, s2, s3, find whether s3 is formed by the interleaving of s1 and s2. For ...

  5. Leetcode 8 String to Integer (atoi) 字符串处理

    题意:将字符串转化成数字. 前置有空格,同时有正负号,数字有可能会溢出,这里用long long解决(leetcode用的是g++编译器),这题还是很有难度的. class Solution { pu ...

  6. [LeetCode] Interleaving String 解题思路

    Given s1, s2, s3, find whether s3 is formed by the interleaving of s1 and s2. For example,Given:s1 = ...

  7. [LeetCode] Interleaving String [30]

    题目 Given s1, s2, s3, find whether s3 is formed by the interleaving of s1 and s2. For example, Given: ...

  8. [LeetCode] Backspace String Compare 退格字符串比较

    Given two strings S and T, return if they are equal when both are typed into empty text editors. # m ...

  9. 【LeetCode】String to Integer (atoi)(字符串转换整数 (atoi))

    这道题是LeetCode里的第8道题. 题目要求: 请你来实现一个 atoi 函数,使其能将字符串转换成整数. 首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止. 当我们 ...

随机推荐

  1. Java I/O and NIO [reproduced]

    Java I/O and NIO.2---Five ways to maximize Java NIO and NIO.2---Build more responsive Java applicati ...

  2. nginx简易教程

    概述 什么是nginx? Nginx (engine x) 是一款轻量级的Web 服务器 .反向代理服务器及电子邮件(IMAP/POP3)代理服务器. 什么是反向代理? 反向代理(Reverse Pr ...

  3. 控制反转、依赖注入、Unity容器

    控制反转原则 依赖注入 Install-Package Unity:https://www.nuget.org/packages/Unity/ Github:https://github.com/un ...

  4. 《连载 | 物联网框架ServerSuperIO教程》- 12.服务接口的开发,以及与云端双向交互

    1.C#跨平台物联网通讯框架ServerSuperIO(SSIO)介绍 <连载 | 物联网框架ServerSuperIO教程>1.4种通讯模式机制. <连载 | 物联网框架Serve ...

  5. spider RPC高级特性

    多租户 spider原生支持多租户部署,spider报文头对外开放了机构号.系统号两个属性用于支持多租户场景下的路由. 多租户场景下的路由可以支持下述几种模式: n  系统号: n  系统号+服务号( ...

  6. jQuery+css3侧边栏导航菜单

    效果体验:http://hovertree.com/texiao/jquery/37/ 代码如下: <!doctype html> <html lang="zh" ...

  7. SharePoint 2013 开发教程

    做了SharePoint有三年了,大家经常会问到,你的SharePoint是怎么学的,想想自己的水平,也不过是初级开发罢了.因为,SharePoint开发需要接触的东西太多了,Windows操作系统. ...

  8. 在xampp中配置dvwa

    DVWA主要是用于学习Web的常见攻击,比如SQL注入.XSS等的一个渗透测试系统,下面我将结合XAMPP来说明它的安装过程. 一.环境 OS:Windows 10 XAMPP:5.6.24 DVWA ...

  9. 可空类型(Nullable<T>)及其引出的关于explicit、implicit的使用

    问题一:Nullable<T>可赋值为null 先看两行C#代码 int? i1 = null; int? i2 = new int?(); int? 即Nullable<int&g ...

  10. 手机游戏渠道SDK接入工具项目分享(三)拨开云雾是个坑

    一直在纠结是先写框架设计还是先写掉过的坑,最后本这娱乐大众的态度先写掉过的坑让大家乐呵下. 项目开发过程中遇问题无数,回顾下8个大坑照成了项目一定程度上延期甚至返工. 项目一开始几个人把现有3家主流的 ...