87. 扰乱字符串
给定一个字符串 s1,我们可以把它递归地分割成两个非空子字符串,从而将其表示为二叉树。
下图是字符串 s1 = “great” 的一种可能的表示形式。
great
/ \
gr eat
/ \ / \
g r e at
/ \
a t
在扰乱这个字符串的过程中,我们可以挑选任何一个非叶节点,然后交换它的两个子节点。
例如,如果我们挑选非叶节点 “gr” ,交换它的两个子节点,将会产生扰乱字符串 “rgeat” 。
rgeat
/ \
rg eat
/ \ / \
r g e at
/ \
a t
我们将 "rgeat” 称作 “great” 的一个扰乱字符串。
同样地,如果我们继续交换节点 “eat” 和 “at” 的子节点,将会产生另一个新的扰乱字符串 “rgtae” 。
rgtae
/ \
rg tae
/ \ / \
r g ta e
/ \
t a
我们将 "rgtae” 称作 “great” 的一个扰乱字符串。
给出两个长度相等的字符串 s1 和 s2,判断 s2 是否是 s1 的扰乱字符串。
示例 1:
输入: s1 = "great", s2 = "rgeat"
输出: true
示例 2:
输入: s1 = "abcde", s2 = "caebd"
输出: false
递归:复杂度N!
memo的判断在于剪枝——防止超时;
class Solution {
public:
bool isScramble(string s1, string s2) {
if (s1 == s2) //终止条件——相同
return true;
int n = s1.size();
int memo[26] = {0};
for (int i = 0; i < n; ++i) {
++memo[s1[i] - 'a'];
--memo[s2[i] - 'a'];
}
for (int i = 0; i < 26; ++i) {
if (memo[i] != 0) //若两者含有的字符不同。直接返回false
return false;
}
for (int i = 1; i < n; ++i) {
if ((isScramble(s1.substr(0, i), s2.substr(0, i)) && isScramble(s1.substr(i), s2.substr(i))) || (isScramble(s1.substr(0, i), s2.substr(n - i)) && isScramble(s1.substr(i), s2.substr(0, n - i)))) {
return true;
}
//(isScramble(s1.substr(0, i), s2.substr(0, i)) && isScramble(s1.substr(i), s2.substr(i))
//这是s1的0~i与后面不交换
//(isScramble(s1.substr(0, i), s2.substr(n - i)) && isScramble(s1.substr(i), s2.substr(0, n - i))
//s1的0~i与n-i~n交换
}
return false;
}
};
不剪枝会超时
class Solution {
public:
bool isScramble(string s1, string s2) {
if(s1==s2) return true;
int n=s1.size();
for(int i=1;i<n;i++) //取1~n-1个字母做前半子树
{
if((isScramble(s1.substr(0,i),s2.substr(0,i))&&isScramble(s1.substr(i),s2.substr(i)))||(isScramble(s1.substr(n-i),s2.substr(0,i))&&isScramble(s1.substr(0,n-i),s2.substr(i))))
//前i个和前i个,后n-i个和后n-i个
//前i个和后i个,后n-i个和前n-i个
return true;
}
return false;
}
};
改为动态规划:从底至上推
dp[i][j][len]代表从s1【i】和s2【j】开始长为len的字串是否可以在交换后相等;
用k遍历每一个len,尝试将dp[i][j][len]中每两个字串互换,若有一个成功,说明dp[i][j][len]可以通过交换相等,则为true;
为此,需要初始化所有len=1的状态;
(1)不交换,dp[i][j][k]&&dp[i+k][j+k][len-k];
(2)交换,dp[i][j+len-k][k]&&dp[i+k][j][len-k];
若有一个k使得上述其中一个为true,则该ijlen也为true;
注意点
(1)初始化所有len为1的状态;
(2)len从2开始递推,直到n(最多n个数);
(3)i和j都从0~n-len;(因为len>=2,所以i,j最多到n-2,右边起码有一个数——n-1);
(4)k从1~len;(子树最少包含一个数);
class Solution {
public:
bool isScramble(string s1, string s2) {
int n=s1.length();
if(n!=s2.length()) return false;
if(s1==s2) return true;
dp.resize(n,vector<vector<bool>>(n,vector<bool> (n+1,0)));
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
if(s1[i]==s2[j])
dp[i][j][1]=1;
for(int len=2;len<=n;len++)
for(int i=0;i<=n-len;i++) //i+len最多为n-1
for(int j=0;j<=n-len;j++)
for(int k=1;k<len;k++)
{
if(dp[i][j][k]&&dp[i+k][j+k][len-k]){
dp[i][j][len]=1;
break;
}
if(dp[i][j+len-k][k]&&dp[i+k][j][len-k])
{
dp[i][j][len]=1;
break;
}
}
return dp[0][0][n];
}
private:
vector<vector<vector<bool>>> dp;
//dp[i][j][k]代表从s1的i开始长度为k的字符串与从s2的j开始长度为k的字符串是否相等
};

本文讨论了如何判断两个长度相等的字符串s1和s2是否是扰乱字符串,即s1通过子节点交换能否变为s2。提出了从递归解法(复杂度N!)到动态规划的优化思路,详细解释了动态规划的实现,包括初始化状态、遍历过程以及关键条件判断。重点强调了动态规划的剪枝策略和正确设置边界条件。

被折叠的 条评论
为什么被折叠?



