旋变字符串
题目描述
一个字符串可以分解为多种二叉树。如果str长度为1,认为不可分解;如果str长度为N(N>1),左半部分长度可以为1~N-1,右半部分为剩下的长度,然后你可以交换左右两部分。并且左右部分可以按照同样的逻辑,继续分解。每一个形成的字符串都可以是原字符串的旋变字符串。现在给你两个字符串str1和str2,判断str2是否为str1的旋变字符串。
输入描述:
输入包括两行,第一行一个字符串,代表 s t r 1 ( 1 ≤ l e n g t h s t r 1 ≤ 100 ) str1( 1 \leq length_{str1} \leq 100 ) str1(1≤lengthstr1≤100),第二行一个字符串,代表 s t r 2 ( 1 ≤ l e n g t h s t r 2 ≤ 100 ) str2(1 \leq length_{str2} \leq 100) str2(1≤lengthstr2≤100)。
输出描述:
如果str2是str1的旋变字符串请输出“YES”,否则输出“NO”。
示例1
输入
abcd
dbac
输出
YES
说明
abcd->d...abc->d...ab...c->d...b...a...c
示例2
输入
IJz
JzI
输出
YES
说明
左边为I右边为Jz交换 变JzI
备注:
时间复杂度 O ( N 4 ) O(N^4) O(N4),额外空间复杂度 O ( N 3 ) O(N^3) O(N3)。
题解:
首先判断 str1 和 str2 长度是否相等,不相等肯定不满足。然后判断包含的字符种类是否一样且每一种字符出现的数量也一样,不满足的话,str2 一定不是 str1 的旋变字符串。
我们可以尝试爆搜去解决这个问题:假设在 i 位置划分 str1 ,字符串长度为 n , 分以下两种情况:
- str1[0…i] 与 str2[0…i] 互为旋变,且 str1[i+1…n-1] 与 str2[i+1…n-1] 互为旋变,则 str1 和 str2 互为旋变字符串;
- str1[0…i] 与 str2[n-i-1…n-1] 互为旋变,且 str1[i+1…n-1] 与 str2[0…n-i-2] 互为旋变,则 str1 和 str2 互为旋变字符串。
爆搜代码:
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 101;
const int M = 256;
char s1[N], s2[N];
int nums[M];
bool is_ok() {
for ( int i = 0; s1[i]; ++i )
++nums[s1[i]];
for ( int i = 0; s2[i]; ++i ) {
if ( --nums[s2[i]] < 0 )
return false;
}
return true;
}
bool dfs( int l1, int l2, int k ) {
if ( k == 1 ) return s1[l1] == s2[l2];
for ( int i = 1; i < k; ++i ) {
if ( (dfs( l1, l2, i ) && dfs( l1 + i, l2 + i, k - i))
||
(dfs( l1, l2 + k - i, i) && dfs( l1 + i, l2, k - i)) )
return true;
}
return false;
}
int main(void) {
scanf("%s", s1);
scanf("%s", s2);
int len1 = strlen( s1 );
int len2 = strlen( s2 );
if ( (len1 != len2) || !is_ok()) {
puts("NO");
return 0;
}
puts( dfs( 0, 0, len1 ) ? "YES" : "NO" );
return 0;
}
这种时间复杂度直接爆炸,肯定没法过。。。
因为搜索过程中涉及到大量的重复状态,于是我们可以使用记忆化搜索, f [ l 1 ] [ l 2 ] [ k ] f[l1][l2][k] f[l1][l2][k] 表示 str1 从 l1 开始,str2 从 l2 开始,连续 k 个字符组成的子串是否互为旋变,f 值为 -1 表示这个状态未出现过。
记忆化搜索代码:
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 101;
const int M = 256;
char s1[N], s2[N];
int nums[M];
int f[N][N][N];
int len1, len2;
bool is_ok() {
for ( int i = 0; s1[i]; ++i )
++nums[s1[i]];
for ( int i = 0; s2[i]; ++i ) {
if ( --nums[s2[i]] < 0 )
return false;
}
return true;
}
bool dfs( int l1, int l2, int k ) {
if ( f[l1][l2][k] != -1 ) return f[l1][l2][k];
if ( k == 1 ) return s1[l1] == s2[l2];
bool ans = false;
for ( int i = 1; i < k; ++i ) {
ans |= ( dfs( l1, l2, i) && dfs( l1 + i, l2 + i, k - i) );
if ( ans ) return f[l1][l2][k] = ans;
ans |= ( dfs( l1, l2 + k - i, i) && dfs( l1 + i, l2, k - i) );
if ( ans ) return f[l1][l2][k] = ans;
}
return f[l1][l2][k] = false;
}
int main(void) {
scanf("%s", s1);
scanf("%s", s2);
len1 = strlen( s1 );
len2 = strlen( s2 );
if ( len1 != len2 || !is_ok() ) {
puts("NO");
return 0;
}
memset( f, -1, sizeof f );
puts( dfs( 0, 0, len1 ) ? "YES" : "NO" );
return 0;
}
记忆化搜索是动态规划的一种写法,我们也可以改写成传统的动态规划写法:
在爆搜代码中:dfs(a, b, size) 依赖的子状态假设为 dfs(c, d, size’) ,size’ 总是小于 size 。说明在计算 f [ a [ b ] [ k ] f[a[b][k] f[a[b][k] 时,这个状态在计算时所依赖的状态一定位于前 k-1 层中,即 size < k 中的某个状态。所以我们可以从 size=1层开始依次推出 size=2层、size=3 层、…、size=n 层,并且同一层之间的状态没有任何依赖关系。
动态规划代码:
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 101;
const int M = 256;
char s1[N], s2[N];
int nums[M];
bool f[N][N][N];
int len1, len2;
bool is_ok() {
for ( int i = 0; s1[i]; ++i )
++nums[s1[i]];
for ( int i = 0; s2[i]; ++i ) {
if ( --nums[s2[i]] < 0 )
return false;
}
return true;
}
bool solve() {
if ( len1 != len2 ) return false;
if ( !is_ok() ) return false;
int n = len1;
for ( int i = 0; i < n; ++i )
for ( int j = 0; j < n; ++j )
f[i][j][1] = (s1[i] == s2[j]);
for ( int sz = 2; sz <= n; ++sz ) {
for ( int l1 = 0; l1 <= n - sz; ++l1 ) {
for ( int l2 = 0; l2 <= n - sz; ++l2 ) {
for ( int i = 1; i < sz; ++i ) {
if ( (f[l1][l2][i] && f[l1 + i][l2 + i][sz - i])
||
(f[l1][l2 + sz - i][i] && f[l1 + i][l2][sz - i])
) {
f[l1][l2][sz] = true;
break;
}
}
}
}
}
return f[0][0][n];
}
int main(void) {
scanf("%s", s1);
scanf("%s", s2);
len1 = strlen( s1 );
len2 = strlen( s2 );
puts( solve() ? "YES" : "NO" );
return 0;
}
至此,《程序员代码面试指南》完整的刷过一遍,配套 OJ 上的题都写了对应的题解,完结撒花!
2021,希望能做个 offer 收割机2333。