旋变字符串

旋变字符串

题目描述

一个字符串可以分解为多种二叉树。如果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(1lengthstr1100),第二行一个字符串,代表 s t r 2 ( 1 ≤ l e n g t h s t r 2 ≤ 100 ) str2(1 \leq length_{str2} \leq 100) str2(1lengthstr2100)

输出描述:

如果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。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值