87-Scramble-String

原题链接

0x0 题目详情

给定一个字符串 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” 称作 "great" 的一个扰乱字符串。

给出两个长度相等的字符串 s1 和 s2,判断 s2 是否是 s1 的扰乱字符串。

测试用例:

示例 1: 输入: s1 = "great", s2 = "rgeat" 输出: true

示例 2: 输入: s1 = "abcde", s2 = "caebd" 输出: false

0x1 解题思路

这道题真的把我看傻了,暴力解都不知道咋写。网上比较普遍的解法如下:

  1. 首先字符串s1和字符串s2的长度必须相同,否则二者不可能是扰乱字符串

  2. 字符串s1和字符串s2中的字符种类以及每种字符的个数必须相同,否则同理,二者不可能是扰乱字符串

  3. 最终的两步如下:

    • 我们先任意找到一个切割点,下图以re之间为切割点:

      first

      可以发现,一个字符串被扰乱之后,只有s1的左半部分经过种种扰乱能够变为字符串s2的左半部分。而又半部分没有扰乱。所以只需要递归s1的左半部分与s2的左半部分查看是否能够扰乱成功。同理,如果扰乱的部分在s1的右半部分,只需要递归判断s1的右半部分与s2的右半部分即可。我们可以把这种情况称之为左对左,右对右

    • 当然还有第二种情况:把s1的左半部分扰乱后的结果对应s2的右半部分(要求s1s2是相同切割点),如下图所示:

      second

      所以在递归时需要判断s1的左半部分与s2的右半部分(要求长度相同),或者递归判断s1的右半部分与s2的左半部分,当然也要求长度相同。

这道题从递归改动态规划有点困难,具体在代码实现部分讲解。

0x2 代码实现

递归版本:

动态规划版本:

这是递归版本的核心代码:

注意:在求length时,这里的字符串s1每一次递归都是不一样的,需要根据当时递归传进去的字符串求值,不是一个固定值。 所以在上面代码的最外层还得套一层跟字符串s1长度相关的循环,从而能够模拟递归。

这里盗张图: dp-up

跟递归思路还是很像的,假如我们当前处理的子字符串长度为q,那么就分为左对左、右对右或者交叉情况。

这里就得使用两个变量i、j表示字符串s1s2的下标。表示从i开始q个长度与从j开始q个长度是否符合规则。所以dp数组是个三维的,dp[len][p][q],表示从p和q开始,[p,p+len)、[q,q+len)是否符合规则。

更新(2020-08-05 22:28:47): 我一直不懂为什么设置i和j两个变量来分别表示s1s2的下标,递归中用的不都是同一个i吗?这一点困扰了我很长很长时间。but,我刚才简单模拟了一下递归的过程后,确实出现了s1s2每个下标的两两匹配。

比如当长度len为2时,q的值可以为1和2,那么四次调用递归函数时,s1和s2的字串下标范围为:

可以看到,s1的每个下标i都与s2的每个下标j进行了两两匹配。而且当取出字符串的长度为len时,那么取出的这个字符串有效下标就为[i,i+len)

0x3 课后总结

这写个跟X一样,靠,恶心死我了。

Last updated

Was this helpful?