[leetcode] 87. Scramble String

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.

今天的题目是判断两字符串能否通过特定方式相互转换,题目难度为Hard。


比较直观的想法是将两个字符串在任意位置一分为二,s1分为s11和s12,s2分为s21和s22,存在两种满足条件的可能性:

  • s11和s21对应,s12和s22对应,s11和s21长度相同,两对字符串能否分别相互转换;
  • s11和s22对应,s12和s21对应,s11和s22长度相同,两对字符串能否分别相互转换;

这样采用分治法的思想将问题化为相似的子问题来解决,通过递归即可求出最终结果。提交之后超时了,需要进行优化。


两个字符串如果能够相互转换,则两者中所含的字符种类和个数必定是相同的,在将字符串拆分递归判断之前可以先进行筛选,将不满足该条件的情况去除掉,这样可以大幅减少递归的可能性,提高算法效率,提交之后通过了。具体代码:

class Solution {
public:
    bool isScramble(string s1, string s2) {
        if(s1 == s2) return true;
        int sz = s1.size();
        vector<int> cnt(26, 0);
        for(auto c:s1) ++cnt[c-'a'];
        for(auto c:s2) if(--cnt[c-'a']<0) return false;
        for(int i=1; i<sz; ++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(sz-i)) && isScramble(s1.substr(i), s2.substr(0, sz-i))) 
                return true;
        }
        return false;
    }
};


以上策略通过递归来自顶向下解决问题,递归中会有许多重复的子问题,应该还可以通过动态规划的方法来解决,重要的是找出动态规划的切入点。这里我们用isScr[i][j][k]表示s1从下标i开始长度为k的子串和s2从下标j开始长度为k的子串能否相互转换。k有1到s1.size()种情况,k为1时,isScr[i][j][1]只需判断s1[i]和s2[j]是否相同即可;k大于1时,和上面递归的方法相同,只需要在长度k范围内任意位置将字符串一分为二,然后依然分两种情况进行判断即可,具体的推导公式如下,大家看了应该就明白了:

isScr[i][j][k] = (isScr[i][j][x] && isScr[i+x][j+x][k-x]) || (isScr[i][j+k-x][x] && isScr[i+x][j][k-x]);

其中x是长度为k的子串一分为二后左边部分的长度。这样采用动态规划的方法自底向上也可以求出最终结果。具体代码:

class Solution {
public:
    bool isScramble(string s1, string s2) {
        int sz = s1.size();
        vector<vector<vector<bool>>> isScr(sz, vector<vector<bool>>(sz, vector<bool>(sz+1, false)));
        for(int k=1; k<=sz; ++k) {
            for(int i=0; i+k<=sz; ++i) {
                for(int j=0; j+k<=sz; ++j) {
                    if(k == 1) isScr[i][j][k] = s1[i]==s2[j];
                    else {
                        for(int x=1; x<k; ++x) {
                            isScr[i][j][k] = (isScr[i][j][x] && isScr[i+x][j+x][k-x]) || (isScr[i][j+k-x][x] && isScr[i+x][j][k-x]);
                            if(isScr[i][j][k]) break;
                        }
                    }
                }
            }
        }
        return isScr[0][0][sz];
    }
};


另外,第一种方法中可能会有重复的子问题进行多次计算,这也是我们想到动态规划的原因,应该还可以缓存子问题结果来减少递归可能性进而提高算法效率,感兴趣的同学可以试一下。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值