动态规划(四重循环降为三重)+递归 87. 扰乱字符串

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

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的字符串是否相等
};
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值