题目来源
题目描述
题目解析
分析题意
题意:[扰乱字符串」是指将 s1 中的某些字符经过一系列翻转,能否得到 s2
- 给定一个字符串S,按照树结构每次二分成左右两个部分,直到单个字符
- 在树上某些节点交换左右儿子,可以形成新的字符串
- 判断一个字符串T是否由S经过这样的变化而成
例子1:
这里有两个随机:
- 随机选择下标
- 随机选择下标的结构就是只能对每种情况进行枚举
- 使用循环判断每种情况
- 随机选择是否交换
- 同样是枚举,但是情况少,只有换与不换
- 同样是枚举,但是情况少,只有换与不换
class Solution {
public:
bool isScramble(string s1, string s2) {
}
};
样本对应模型
暴力递归
(1)大的过滤:str1和str2所有字符的种类和个数必须一样
(2)样本对应模型
- 定义
f
(
s
t
r
1
,
L
1
,
R
1
,
s
t
r
2
,
L
2
,
R
2
)
f(str1, L1, R1, str2, L2, R2)
f(str1,L1,R1,str2,L2,R2):
- str1的 L 1 − R 1 L1-R1 L1−R1,对 s t r 2 str2 str2的 L 2 − R 2 L2-R2 L2−R2长度相同,那么
- 判断 s t r 1 [ L 1... R 1 ] str1[L1...R1] str1[L1...R1]和 s t r 2 [ L 2... R 2 ] str2[L2...R2] str2[L2...R2]是不是扰乱字符串
- base case:
- 当只有一个字符时,即 L 1 = = R 1 , L 2 = = R 2 L1 == R1,L2==R2 L1==R1,L2==R2
- 那么 s t r 1 [ L 1 ] = = s t r 2 [ L 2 ] str1[L1] == str2[L2] str1[L1]==str2[L2]时,返回true,否则返回false
- 普通情况:
- 样本对应模型一般都是根据第一刀的情况来划分
- 那么可以用枚举的方式枚举出str1的第一刀切哪里了
class Solution {
bool sameTypeSameNumber(string s1, string s2){
std::vector<int> map(256, 0);
for (int i = 0; i < s1.size(); ++i) {
map[s1[i]]++;
}
for (int i = 0; i < s2.size(); ++i) {
if(--map[s2[i]] < 0){
return false;
}
}
return true;
}
// str1[L1...R1] str2[L2...R2] 是否互为玄变串
// 一定保证这两段是等长的!
bool process(string &str1, int L1, int R1, string &str2, int L2, int R2){
if(L1 == R1){
return str1[L1] == str2[L2];
}
for (int leftEnd = L1; leftEnd < R1; ++leftEnd) {
bool p1 = process(str1, L1, leftEnd, str2, L2, L2 + leftEnd - L1)
&& process(str1, leftEnd + 1, R1, str2, L2 + leftEnd - L1 + 1, R2);
bool p2 = process(str1, L1, leftEnd, str2, R2 - (leftEnd - L1), R2)
&& process(str1, leftEnd + 1, R1, str2, L2, R2 - (leftEnd - L1) - 1);
if (p1 || p2) {
return true;
}
}
return false;
}
public:
bool isScramble(string str1, string str2) {
if(str1.empty() && str2.empty() || str1 == str2){
return true;
}
if(str1.size() != str2.size()){
return false;
}
if(!sameTypeSameNumber(str1, str2)){
return false;
}
return process(str1, 0, str1.size() - 1, str2, 0, str2.length() - 1);
}
};
分析,process(string &str1, int L1, int R1, string &str2, int L2, int R2)
有四个参数,我们应该尽可能省掉一些参数,以改成动态规划。
// str1[L1...R1] str2[L2...R2] 是否互为玄变串
// 一定保证这两段是等长的!
bool process(string &str1, string &str2, int L1, int L2, int size){
if(size == 1){
return str1[L1] == str2[L2];
}
// 枚举每一种情况,有一个计算出互为旋变就返回true。都算不出来最后返回false
for (int leftPart = 1; leftPart < size; leftPart++) {
if (
// 如果1左对2左,并且1右对2右
(process(str1, str2, L1, L2, leftPart)
&& process(str1, str2, L1 + leftPart, L2 + leftPart, size - leftPart))
||
// 如果1左对2右,并且1右对2左
(process(str1, str2, L1, L2 + size - leftPart, leftPart)
&& process(str1, str2, L1 + leftPart, L2, size - leftPart))) {
return true;
}
}
return false;
}
递归
从一个位置将两个字符串分别划分成两个子串,然后递归判断两个字符串的两个子串是否互相为「扰乱字符串」。
(1)递归出口
- 两个字符串长度不相等,false
- 两个字符串equal,true
- 两个字符串包含的字符(不论顺序)不同,false
(2)如何随机选择下标
- 因为不知道在哪个位置分割字符串,所以直接遍历每个位置进行分割
- 在判断是否两个子串能否通过翻转变成相等的时候,需要保证传给函数的子串长度是相同的。
(3)考虑随机选择是否交换
- 分别将s1中交换的与不交换的情况与s2对应的情况分别进行递归
综上,因此分两种情况讨论:
- s1[0:i]和 s2[0:i]作为左子树,s1[i:N]和 s2[i:N]作为右子树
- s1[0:i]和 s2[N - i:N]作为左子树,s1[i:N]和 s2[0:N-i]作为右子树
其中左子树的两个字符串的长度都是 i, 右子树的两个字符串的长度都是 N - i。如果上面两种情况有一种能够成立,则 s1 和 s2 是「扰乱字符串」。
class Solution {
unordered_map<string, unordered_map<string, bool>> memo;
public:
bool isScramble(string s1, string s2) {
if(memo.count(s1) && memo[s1].count(s2)){
return memo[s1][s2];
}
// 长度不等,必定不能变换
if (s1.length() != s2.length()) {
return memo[s1][s2] = false;
}
// 长度相等,先特判下
if (s1 == s2) {
return memo[s1][s2] = true;
}
// 看一下字符个数是否一致,不同直接return false
std::map<char, int> map;
int n = s1.length();
for (int i = 0; i < n; ++i) {
char c1 = s1[i], c2 = s2[i];
map[c1]++;
map[c2]--;
}
for(auto i : map){
if(i.second != 0){
return memo[s1][s2] = false;
}
}
// 相同的话,开始判断判断,满足一个就能 return true
for (int i = 1; i < n; ++i) {
// / S1 -> T1,S2 -> T2
bool choose1 = isScramble(s1.substr(0, i), s2.substr(0, i)) &&
isScramble(s1.substr(i), s2.substr(i));
// S1 -> T2,S2 -> T1
bool choose2 = isScramble(s1.substr(0, i),s2.substr(n - i)) &&
isScramble(s1.substr(i), s2.substr(0, n - i));
if (choose1 || choose2) return memo[s1][s2] = true;
}
return memo[s1][s2] = false;
}
};
动态规划
这是一道区间型状态规划:
- 给定一个序列或者字符串进行一些操作,从最后一步出发,将序列或者字符串去头、去尾…
- 区间型dp一般用dp[i][j],i表示左端点,j表示右端点,如果有其他维度可以再添加,如果两个端点之间有联系,则可以再压缩空间
(1)确定状态
给定两个字符串 T和 S
-
显然,如果T.length != S.length,那么false
-
如果长度一样,并且我们知道S最上层二分被分为S= S 1 S 2 S_1S_2 S1S2,同样T也有两部分 T = T 1 T 2 T=T_1T_2 T=T1T2,那么一定有:
- 情况一:没有交换, T 1 T_1 T1是 S 1 S_1 S1变换而来的, T 2 T_2 T2是由 S 2 S_2 S2变换而来的
- 情况二:交换了, T 1 T_1 T1是 S 2 S_2 S2变换而来的, T 2 T_2 T2是由 S 1 S_1 S1变换而来的
-
子问题:
- 要求T是否由S变化而来
- 情况一:需要知道 T 1 T_1 T1是否 S 1 S_1 S1变换而来的, T 2 T_2 T2是否由 S 2 S_2 S2变换而来的
- 情况二:需要知道 T 1 T_1 T1是 S 2 S_2 S2变换而来的, T 2 T_2 T2是由 S 1 S_1 S1变换而来的
- T 1 、 S 1 、 T 2 、 S 2 T_1、S_1、T_2、S_2 T1、S1、T2、S2长度更短
- 状态:
-
d
p
[
i
]
[
j
]
[
k
]
[
h
]
dp[i][j][k][h]
dp[i][j][k][h]表示
T
[
k
.
.
.
h
]
T[k...h]
T[k...h]是否由
S
[
i
.
.
.
j
]
S[i...j]
S[i...j]变化而来。
- 由于变换必须长度是一样的,因此这边有个关系 j - i = h - k,可以把四维数组变为三维,每个串都可以用(起始位置,长度)表示。例如:
- S 1 S_1 S1长度为5,在S中位置7开始
- T 1 T_1 T1长度为5,在T中位置0开始
- 可以用f[7][0][5]=true/false表示 S 1 S_1 S1能够变化为 T 1 T_1 T1
- 由于变换必须长度是一样的,因此这边有个关系 j - i = h - k,可以把四维数组变为三维,每个串都可以用(起始位置,长度)表示。例如:
- 因此:设 f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]表示从字符串 S S S中i开始长度为k的字符串是否能变换为从字符串 T T T中开始的长度为 k k k的字符串
-
d
p
[
i
]
[
j
]
[
k
]
[
h
]
dp[i][j][k][h]
dp[i][j][k][h]表示
T
[
k
.
.
.
h
]
T[k...h]
T[k...h]是否由
S
[
i
.
.
.
j
]
S[i...j]
S[i...j]变化而来。
(2)转移方程
(3)初始条件
- 对于长度是 1 的子串,只有相等才能变过去,相等为 truetrue,不相等为 falsefalse。
(4)得到答案
class Solution {
public:
bool isScramble(string s1, string s2) {
if(s1 == s2) return true;
if(s1.size() != s2.size()) return false;
int n = s1.size();
vector<vector<vector<int>>> f(n,
vector<vector<int>>(n,
vector<int>(n+1)));
// 初始化单个字符的情况
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
f[i][j][1] = (s1[i] == s2[j]);
}
}
// 枚举区间长度 2~n
for(int len = 2; len <= n; len++) {
// 枚举 S 中的起点位置
for(int i = 0; i <= n - len; i++) {
// 枚举 T 中的起点位置
for(int j = 0; j <= n - len; j++) {
// 枚举划分位置
for(int k = 1; k <= len - 1; k++) {
// 第一种情况:S1 -> T1, S2 -> T2
if (f[i][j][k] && f[i + k][j + k][len - k]) {
f[i][j][len] = true;
break;
}
// 第二种情况:S1 -> T2, S2 -> T1
// S1 起点 i,T2 起点 j + 前面那段长度 len-k ,S2 起点 i + 前面长度k
if (f[i][j + len - k][k] && f[i + k][j][len - k]) {
f[i][j][len] = true;
break;
}
}
}
}
}
return f[0][0][n];
}
};
类似题目
题目 | 思路 |
---|---|
leetcode:87. 扰乱字符串 | |
leetcode:97. 交错字符串 |