87、扰乱字符串-dp

  1. 示例 1:
  2. 示例 2:
  • 思路
  • 代码
  • 给定一个字符串 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
    

    思路

    • 两个字符串,全局搜索
    • 单元子问题:两个字符是否相等
    • 递归算法
      • 递归结束判断
        • 字符串是否为空
        • 字符串长度是否相等
        • 字符串直接相等
        • 字符串包含字符数量和种类是否一致
      • 字符串遍历,从1开始
        • 递归匹配字串
          • 递归1 0~i 和 0~i匹配 && ilen 和 ilen匹配
          • 递归2 0i 和 len-ilen匹配 && ilen 和 0len-i匹配

    代码

    • 全局递归
    class Solution {
        public boolean isScramble(String s1, String s2) {
            // 在扰乱这个字符串的过程中,我们可以挑选任何一个非叶节点,然后交换它的两个子节点。
            // 全局递归匹配、问题细化、递归完成
            if (s1 == null || s2 == null) return false;
            if (s1.length() != s2.length()) return false;
            if (s1.equals(s2)) return true;
            int[] chs = new int[26];
            int len = s1.length();
            for (int i = 0;i < len;i++) {
                chs[s1.charAt(i) - 'a']++;
                chs[s2.charAt(i) - 'a']--;
            }
            
            for (int i = 0;i < 26;i++) {
                if (chs[i] != 0) {
                    return false;
                }
            }
            
            // 从1可开始,要不然会死循环
            for (int i = 1;i < len;i++) {
                // 正常情况 0~i 0~i && i~len i~len
                if (isScramble(s1.substring(0,i),s2.substring(0,i)) && isScramble(s1.substring(i),s2.substring(i))) {
                    return true;
                }
                // 发生了交换 0~i len-i~len && i~len 0~len-i
                if (isScramble(s1.substring(0,i),s2.substring(len-i)) && isScramble(s1.substring(i),s2.substring(0,len-i))) {
                    return true;
                }
            }
            return false;
        }
    }
    
    //
    class Solution {
        public boolean isScramble(String s1, String s2) {
            if (s1 == null || s2 == null) return false;
            if (s1.length() != s2.length()) return false;
            if (s1.equals(s2)) return true;
            int[] letters = new int[26];
            for (int i = 0; i < s1.length(); i++) {
                letters[s1.charAt(i) - 'a']++;
                letters[s2.charAt(i) - 'a']--;
            }
            for (int i = 0; i < 26; i++) {
                if (letters[i] != 0) return false;
            }
            for (int i = 1; i < s1.length(); i++) {
                if (isScramble(s1.substring(0, i), s2.substring(0, i)) && isScramble(s1.substring(i), s2.substring(i)))
                    return true;
                if (isScramble(s1.substring(0, i), s2.substring(s2.length() - i)) && isScramble(s1.substring(i), s2.substring(0, s2.length() - i)))
                    return true;
            }
            return false;   
        }
    }
    
    //  作者:powcai
    // 链接:https://leetcode-cn.com/problems/scramble-string/solution/di-gui-by-powcai/
    // 来源:力扣(LeetCode)
    // 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 
    
    • 动态规划
    1. dp[len][i][j]代表s1与s2的两个长度为len的片段是否是为扰乱字符串对

      其中,s1以i起始,s2以j起始,也就是s1[i:(i + len - 1)], s2[j:(j + len - 1)]这两段。

    2. 状态转移方程为:
      dp[len][j][j] ||= (dp[k][i][j] && dp[len - k][i + k][j + k]) || (dp[k][i][j + len - k] && dp[len - k][i + k][j])

      其中k>=1k < len
      空间复杂度 O(N^3),时间复杂度 O(N^4)

    3. 算法
    • 状态初始化
      • N+1 层状态
      • 第一层 逐个位置匹配
    • 枚举状态更新
      • 层循环 len
        • s1 循环 i
          • s2循环 j
            • 遍历已经确定状态的层 k
              • 正常匹配状态更新
              • 扰乱匹配状态更新
    class Solution {
    public:
        bool isScramble(string s1, string s2) {
            if (s1.size() != s2.size()) return false;
            if (s1.empty()) return true;
            int N = s1.size();
            // 三维动态数组,存储状态
            vector<vector<vector<bool> > > dp(N + 1, vector<vector<bool> >(N, vector<bool>(N, false)));
            // 初始化
            for (int i = 0; i < N; ++i) {
                for (int j = 0; j < N; ++j) {
                    dp[1][i][j] = s1[i] == s2[j];
                }
            }
            for (int len = 2; len <= N; ++len) { // len 2~N
                for (int i = 0; i < N && i + len - 1 < N; ++i) { // i 0~N-1
                    for (int j = 0; j < N && j + len - 1 < N; ++j) {// j 0~N-1
                        for (int k = 1; k < len; ++k) { // k 1~len-1
                            if (dp[k][i][j] && dp[len - k][i + k][j + k]) {
                                dp[len][i][j] = true;
                                break;
                            }
                            if (dp[k][i][j + len - k] && dp[len - k][i + k][j]) {
                                dp[len][i][j] = true;
                                break;
                            }
                        }
                    }
                }
            }
            return dp[N][0][0];
        }
    };
    
    // 作者:da-li-wang
    // 链接:https://leetcode-cn.com/problems/scramble-string/solution/c-dong-tai-gui-hua-by-da-li-wang-36/
    // 来源:力扣(LeetCode)
    // 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    
    // java 版
    class Solution {
        public boolean isScramble(String s1, String s2) {
            // 在扰乱这个字符串的过程中,我们可以挑选任何一个非叶节点,然后交换它的两个子节点。
            // 全局递归匹配、问题细化、递归完成
            if (s1 == null || s2 == null) return false;
            if (s1.length() != s2.length()) return false;
            if (s1.equals(s2)) return true;
            int N = s1.length();
            
            boolean[][][] dp = new boolean[N+1][N][N];
            // 初始化状态
            for (int i = 0;i < N;i++) {
                for(int j = 0;j < N;j++) {
                    // dp[1][i][j] 第一层 记录 s[i] 与 s[j] 是否相等
                    dp[1][i][j] = s1.charAt(i) == s2.charAt(j);
                }
            }
            
            // 枚举 字符片段是否 全局匹配
            for (int len = 2; len <= N;len++) {
                for (int i = 0;i < N && i + len - 1 < N;i++) {
                    for (int j = 0;j < N && j + len - 1 < N;j++) {
                        for (int k = 1; k < len;k++) {
                            if (dp[k][i][j] && dp[len - k][i + k][j + k]) {
                                dp[len][i][j] = true;
                                break;
                            }
                            if (dp[k][i][j + len - k] && dp[len - k][i + k][j]) {
                                dp[len][i][j] = true;
                                break;
                            }
                        }
                    }
                }
            }
            
            return dp[N][0][0];
        }
    } 
    

    转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1056615746@qq.com

    💰

    Title:87、扰乱字符串-dp

    Count:1.6k

    Author:攀登

    Created At:2020-07-26, 00:19:44

    Updated At:2024-06-15, 15:52:32

    Url:http://jiafeimao-gjf.github.io/2020/07/26/87%E3%80%81%E6%89%B0%E4%B9%B1%E5%AD%97%E7%AC%A6%E4%B8%B2-dp/

    Copyright: 'Attribution-non-commercial-shared in the same way 4.0' Reprint please keep the original link and author.

    ×

    Help us with donation