97. Interleaving String
Given s1, s2, s3, find whether s3 is formed by the interleaving of s1 and s2.
For example,
Given:
s1 = “aabcc”,
s2 = “dbbca”,
When s3 = “aadbbcbcac”, return true.
When s3 = “aadbbbaccc”, return false.
题目内容:
给定3个字符串s1, s2, s3,判断s3是否通过s1和s2的子字符串交替组合来生成的一个字符串,例如
s1 = “aabcc”
s2 = “dbbca”
s3 = “aadbbcbcac”
那么s3可以看成
s3 = s1[0] + s1[1] + s2[0] + s2[1] + s2[2] + s2[3] + s1[2] + s1[3] + s2[4] + s1[4]
但是当s3 = “aadbbbaccc”的时候,无法由s1和s2的字符组合生成。
解题思路:
用2个字符串s1和s2来组合成s3的过程,可以看成是一个从由s1和s2的字符组成的二叉树中查找是否存在一条从根节点出发的路径到叶子结点经过的字符组合成s3的过程,假如s1 = “ab”, s2 = “a”则我们可以简单地构建一个二叉树,左边表示选取s1的下一个字符,右边表示选取s2的下一个字符,因为s1只有2个字符,s1只有1个字符,所以每条路径都由2条左边和1条右边组成,如下图:
可以看出,s1和s2可以组合成”aba”, “aab”,所以当s3是这两种字符串的一种的时候,我们都能找到对应的路径 ,这时候问题就变成是二叉树路径的搜索问题,有个简单的方法就是深度优先算法(DFS)和宽度优先算法(BFS),这里我选择了DFS作为我解题的方法。在C++中,如果要实现DFS,一个普遍的方法是用一个stack来存储二叉树中每个节点的状态,那么节点的状态应该用什么表示呢?我定义了一个结构体来表示每个结构体的状态,这个结构体记录了当前状态s1和s2下一个可用字符的位置,还有s3下一个需要匹配的位置。
当s1的下一个可用字符与s3下一个要匹配的字符相同,那么将s1的下一个可用位置索引往后移一个位置,s3的下一个要匹配的字符索引也往后移一个位置,并将新的状态压入stack中。对s2也做同样的操作。当s3已经匹配到了最后一个字符,那么返回true,如果stack已经空了,但还没匹配到最后一个字符,那就说明没找到对应的路径,返回false。
这个方法看上去是可行的,但是发现当输入字符串很长的时候,程序会超时。因为采用DFS的时候,最坏的情况可能要遍历所有路径才能得到结果,这时候的时间消耗是非常大的。
但是后来发现例如上述的二叉树例子中,其实V4和V7是等价的,因为这里每到一个状态,我们并不需要关注前面的字符串的匹配过程,只需要关注s1匹配到哪里了,s2匹配到哪里了。假如s3 = “aab”,因为s1 = “ab”, s2 = “a”,所以不管s3第一个’a’是来自s1还是s2,都能组合成最终的”aab”,从树的叶子结点也可以看到,组合成”aab”这种字符串是有2条路径的,所以我们对于V4和V7这两种结点,可以只取一种。
所以,后来我用了一个unordered_set来保存已经遍历过的结点,防止下次出现同样的状态会重复压入stack中。
注意:
因为我用unordered_set保存结点状态的时候,这个状态是我自己定义的结构体,因为unordered_set为了提高检索效率,内部使用了哈希表而不是红黑树,所以如果unordered_set存储的是结构体的时候,需要给这个结构体定义哈希函数和重载==操作。
代码:
#include <iostream>
#include <string>
#include <stack>
#include <unordered_set>
using namespace std;
struct Position {
Position(int a, int b, int c):s1_startIndex(a), s2_startIndex(b), s3_startIndex(c) {}
int s1_startIndex;
int s2_startIndex;
int s3_startIndex;
bool operator==(const Position& p) const {
return this->s1_startIndex == p.s1_startIndex && this->s2_startIndex == p.s2_startIndex;
}
};
//declare hash function of Position
namespace std {
template <>
struct hash<Position> {
size_t operator () (const Position &p) const {
return (std::hash<int>()(p.s1_startIndex) << 1) ^ std::hash<int>()(p.s2_startIndex);
}
};
}
class Solution {
public:
bool isInterleave(string s1, string s2, string s3) {
bool flag = false;
stack<Position> tempStack;
unordered_set<Position> visited;
//首先通过s1,s2和s3的长度来处理一些特殊情况
if(s1.empty() && s2.empty() && s3.empty()) return true;
if(s3.length() == 0 || s1.length() + s2.length() != s3.length()) return false;
if(s1.length() > 0 && s1[0] == s3[0]) {
if(s3.length() == 1) return true;
tempStack.push(Position(1, 0, 1));
visited.insert(Position(1, 0, 1));
}
if(s2.length() > 0 && s2[0] == s3[0]) {
if(s3.length() == 1) return true;
tempStack.push(Position(0, 1, 1));
visited.insert(Position(0, 1, 1));
}
while(!tempStack.empty()) {
Position preState = tempStack.top();
tempStack.pop();
//choose s1 this time
if(preState.s1_startIndex < s1.length()
&& s3[preState.s3_startIndex] == s1[preState.s1_startIndex]) {
if(preState.s3_startIndex + 1 == s3.length()) {
flag = true;
break;
}
Position newPos(preState.s1_startIndex + 1, preState.s2_startIndex,
preState.s3_startIndex + 1);
if(visited.find(newPos) == visited.end()) {
tempStack.push(newPos);
visited.insert(newPos);
}
}
//chose s2 this time
if(preState.s2_startIndex < s2.length()
&& s3[preState.s3_startIndex] == s2[preState.s2_startIndex]) {
if(preState.s3_startIndex + 1 == s3.length()) {
flag = true;
break;
}
Position newPos(preState.s1_startIndex, preState.s2_startIndex + 1,
preState.s3_startIndex + 1);
if(visited.find(newPos) == visited.end()) {
tempStack.push(newPos);
visited.insert(newPos);
}
}
}
return flag;
}
};
int main(int argc, const char * argv[]) {
Solution sln;
cout << sln.isInterleave("aa", "ab", "aaba") << endl;
return 0;
}