每日一题——扰乱字符串

    技术2024-12-08  13

    菜鸡每日一题系列打卡87天

    每天一道算法题目 

    小伙伴们一起留言打卡

    坚持就是胜利,我们一起努力!

    题目描述(引自LeetCode)

    给定一个字符串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

    题目分析

    这道题比较容易想到的方式是递归,基于leetcode测试集提供的s1和s2均由字母组成,且长度比较小时,递归是可行的,而且相对比较容易实现;但当组成s1和s2的字符集变大时,或者长度达到一定规模时,就需要考虑采用动态规划。

    在这里,菜鸡给出了两种解法,供大家参考。话不多说,上代码!

    代码实现

    // 递归 class Solution { public boolean isScramble(String s1, String s2) { // 特殊情况处理 if (s1 == null || s2 == null) return s1 == s2; if (s1.length() != s2.length()) return false; // dfs return dfs(s1, s2, 0, 0, s1.length()); } private boolean dfs(String s1, String s2, int i, int j, int l){ // 递归终止条件 if (l == 1) return s1.charAt(i) == s2.charAt(j); // 剪枝 int[] tmp = new int[26]; for (int k = 0; k < l; k++) { tmp[s1.charAt(i + k) - 'a']++; tmp[s2.charAt(j + k) - 'a']--; } for (int c : tmp) if (c != 0) return false; // 递归 for (int k = 1; k < l; k++) { if (dfs(s1, s2, i, j, k) && dfs(s1, s2, i + k, j + k, l - k)) return true; if (dfs(s1, s2, i, j + l - k, k) && dfs(s1, s2, i + k, j, l - k)) return true; } // 返回 return false; } } // 动态规划 class Solution { public boolean isScramble(String s1, String s2) { // 特殊情况处理 if (s1 == null || s2 == null) return s1 == s2; int n = s1.length(); if (n != s2.length()) return false; // 初始化dp数组 boolean[][][] dp = new boolean[n][n][n + 1]; for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { dp[i][j][1] = s1.charAt(i) == s2.charAt(j); } } // 计算dp数组 for (int l = 2; l <= n; l++) { for (int i = 0; i <= n - l; i++) { for (int j = 0; j <= n - l; j++) { for (int k = 1; k <= l - 1; k++) { if (dp[i][j][k] && dp[i + k][j + k][l - k]) { dp[i][j][l] = true; break; } if (dp[i][j + l - k][k] && dp[i + k][j][l - k]) { dp[i][j][l] = true; break; } } } } } // 返回结果 return dp[0][0][n]; } }

    代码分析

    对代码进行分析,递归方式的时间复杂度为阶乘级别;递归调用占用了大量栈空间,空间复杂度为阶乘级别。

    动态规划方式,计算dp数组需要四层for循环,时间复杂度为O(n^4);同时,空间上需要三维数组进行状态存储,空间复杂度为O(n^3)。

    执行结果

    递归方式的执行结果

    动态规划方式的执行结果

    注意:上述执行结果的差异受leetcode测试集的限制,并不能客观表现递归方式和动态规划方式之间的差异。

    学习 | 工作 | 分享

    ????长按关注“有理想的菜鸡”

    只有你想不到,没有你学不到

    Processed: 0.019, SQL: 9