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. ASP.NET Core 中文文档 第四章 MVC(3.1)视图概述

    原文:Views Overview 作者:Steve Smith 翻译:姚阿勇(Dr.Yao) 校对:高嵩(Jack) ASP.NET MVC Core 的控制器可以利用 视图 返回格式化结果. 什么 ...

  2. shell脚本规划化模板

    shell脚本规划化模板 Linux运维过程中,shell脚本是不可缺少的工具,但是每个运维人员编程的习惯都不一样,很多时候就是实现某个功能,写出来的脚本都是烂七八糟的.脚本必须规范化,应该从以后几个 ...

  3. Kafka无消息丢失配置

    Kafka到底会不会丢数据(data loss)? 通常不会,但有些情况下的确有可能会发生.下面的参数配置及Best practice列表可以较好地保证数据的持久性(当然是trade-off,牺牲了吞 ...

  4. ASP.NET MVC传递参数(model)

    一看到此标题,相信你也会.因为路由是可以从URL地址栏传过去的. 但是Insus.NET不想在地址栏传递,还是一个条件是jQuery的Ajax进行POST的.Insus.NET不清楚别人是怎样处理的, ...

  5. 【手记】为windows2008建个睡眠快捷方式

    ----------------201607041343更新---------------- 在另一台电脑上SetSystemPowerState又不好使了,执行了根本没反应.又回头来试SetSusp ...

  6. 【转】zigbee终端无法重连的问题解决

    zigbee终端无法重连的问题解决 1.zigbee重连的原因 (1)zigbee由于各种原因的干扰导致信号太差而掉线. (2)协调器重启. 2.zigbee终端重连的处理 (1)zigbee掉线后会 ...

  7. 使用do{ } while(0)的好处

    经常看到好多程序,尤其是linux相关的,使用do{}while(0)的写法,很明显内部程序最多只能执行一次,这样写的原因是什么呢?个人认为主要的原因是,如果不使用do{}while(0),那么当一个 ...

  8. iOS项目iCloud及CloudKit Dashboard运用

    CloudKit是苹果推出的基于iCloud的一个云端数据存储服务.其 主要由下面两部分组成: 一个仪表web页面,用于管理公开数据的记录类型. 一组API接口,用于iCloud和设备之间的数据传递. ...

  9. Android中的AlertDialog使用示例五(自定义对话框)

    在Android开发中,我们经常会需要在Android界面上弹出一些对话框,比如询问用户或者让用户选择.这些功能我们叫它Android Dialog对话框,AlertDialog实现方法为建造者模式. ...

  10. iOS学习15之OC集合

    1.数组类 1> 回顾C语言数组 数组是一个有序的集合, 来存储相同数据类型的元素. 通过下标访问数组中的元素,下标从 0 开始. 2> 数组 数组是一个有序的集合,OC中的数组只能存储对 ...