87. 扰乱字符串
题目描述
Given a string s1
, we may represent it as a binary tree by partitioning it to two non-empty substrings recursively.Below is one possible representation of s1 ="great"
:
great
/ \
gr eat
/ \ / \
g r e at
/ \
a t
To scramble the string(爬行字符串), we may choose any non-leaf node(选择任何非叶子节点)
and swap its two children.For example, if we choose the node"gr"and swap its two children,it produces a scrambled string "rgeat"
.
rgeat
/ \
rg eat
/ \ / \
r g e at
/ \
a t
We say that"rgeat"
is a scrambled string of"great"
.Similarly, if we continue to swap the children of nodes"eat"
and"at"
,it produces a scrambled string"rgtae"
.
rgtae
/ \
rg tae
/ \ / \
r g ta e
/ \
t a
We say that"rgtae"
is a scrambled string of"great"
.
Given two strings s1
and s2
of the same length, determine if s2
is a scrambled string of s1
.
方法1
思路
基于传统的递归方法,枚举S1
每一种初始分割方式,一个个的递归判断(类似于树的镜像的判断方法),遇到成功的分割时要及时终止函数(只要有一种情况成功了就行了),简单的说,就是s1
和s2
是scramble
的话,那么必然存在(只要成功就行了)一种分割,将s1
分成s11
和s12
两段,将s2
分成s21
和s22
.那么要么s11
和s21
是scramble
的并且s12
和s22
是scramble
的;s11
和s22
是scramble
的并且s12
和s21
是scramble
的。子问题是同样的问题,继续递归判断下去.
代码实现
class Solution {
public:
bool isScramble(string s1, string s2)
{
if(s1.length()==0 && s2.length()==0)
return true;
if(s1.length()==0 || s2.length()==0)
return false;
if(s1.length() != s2.length())
return false;
if(s1 == s2)
return true;
//另外在进入递归前需要剪枝,判断两个字符串是否包含相同的字母,减少不必要的操作
int c[26] = {0};
for(int i = 0;i < s1.length();i++)
{
c[s1[i]-'a']++;
c[s2[i]-'a']--;
}
for(int i=0;i<26;i++)
if(c[i] != 0)
return false;
//枚举s1的每一种根节点的划分情况
//i为划分左半部分的节点个数
for(int i = 1;i < s1.length();i++)
{
//此种根节点划分情况下,根节点处的两个孩子结点没有交换
if(isScramble(s1.substr(0,i), s2.substr(0,i)) && isScramble(s1.substr(i), s2.substr(i)))
return true;
//此种根节点划分情况下,根节点处的两个孩子结点发生了交换
if(isScramble(s1.substr(0,i), s2.substr(s2.length()-i)) && isScramble(s1.substr(i), s2.substr(0,s2.length()-i)))
return true;
}
return false;
}
};
解法2
思路
动态规划方法
根据以往的经验来说,与符串有关的题十有八九可以用DP
来做,动态规划只是把用递归函数方法中的重复的子问题,需要每次递归解决的用一个中间的数据结构存放临时信息,方便我们需要时直接得到结果。
我们维护量res[n][i][j]
,其中i
是s1
的起始字符下标,j
是s2
的起始字符下标,而n
是当前的字符串长度,递归函数中的子问题s1
和s2
可以是任意位置的子串,而确定一个子串只需要一个起点下标,一个长度即可.有什么样的子问题,就有什么样的dp
数据结构设计.
代码实现
class Solution {
public:
bool isScramble(string s1, string s2)
{
if(s1.length()==0 && s2.length()==0)
return true;
if(s1.length()==0 || s2.length()==0)
return false;
if(s1.length() != s2.length())
return false;
if(s1 == s2)
return true;
int n = s1.size();
vector<vector<vector<bool> > > dp(n+1, vector<vector<bool> >(n, vector<bool>(n,false)));
for(int i = 0; i < n; ++i)
{
for(int j = 0; j < n; ++j)
dp[1][i][j] = s1[i] == s2[j];
}
for(int len = 2; len <= n; ++len)
{
for(int i = 0; i <= n-len; ++i)
{
for(int j = 0; j <= n-len; ++j)
{
//注意这里的递归公式中i,j求解的顺序没有要求,但是len的顺序必须从小到大
for(int k = 1; k < len; ++k)
{
if((dp[k][i][j] && dp[len - k][i+k][j+k]) || (dp[k][i][j+len-k] && dp[len-k][i+k][j]))
{
dp[len][i][j] = true;
break;
}
}
}
}
}
return dp[n][0][0];
}
};