leetcode_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

解题

判断扰乱的前期是 s1,s2的长度要相等

然后将s1,s2分别分割成两部分进行判断,分割出的两部分都不为空

该两部分的长度要对应

下图为一个例子

在这里插入图片描述

  • 图上第一种情况相当于 s1[:i] -> s2[:i] && s1[i:] -> s2[i:]。s1中前i个字符变为了s2中前i个字符,同时,s1中剩余的字符可以变成s2中剩余的字符
  • 图上第二种情况相当于 s1[:i] -> s2[-i:] && s1[i:] -> s2[:-i] 。s1中前i个字符变成了s2中后i个字符,同时,s1中剩余的字符可以变成s2中剩余的字符

将一个问题分割成了小问题

只需判断最底层,即两个字符时能够变换,如,grrg能否变换

设置dp[i][j][k]的三维动态数组

dp[i][j][k] 表示字符串s1中i开始的长度为k的子串能否变换成s2中j开始的长度为k的子串

最后的目标为d[0][0][len],表示从0开始的两个字符串是否成功能变换

dp[i][j][k]可以根据图上的两种情况分为两种情况

  • dp[i][j][w] && dp[i+w][j+w][k-w] 表示图上第一种情况
  • dp[i][j+k-w] && dp[i+w][j][k-w] 表示图上第二种情况

其中,w是在长度为k的子串中的切分位置,1<=w<=k-1

在初始条件下,k=1,即不能再分割,当s1[i]s2[j]相等时,d[i][j][1]取true,否则为false

class Solution:
    def isScramble(self, s1: str, s2: str) -> bool:
        len1 = len(s1)
        len2 = len(s2)
        # s1 s2 字符串长度需相同
        if len1 != len2:
            return False
        # 最里面的长度为len1+1,用不到dp[i][j][0]
        dp = [[[False for _ in range(len1+1)] for _ in range(len1)] for _ in range(len1)]

        # 初始化  
        for i in range(len1):
            for j in range(len1):
                if s1[i] == s2[j]:
                    dp[i][j][1] = True
        
        # 各区间长度,长度为1的单字符已经初始化了,长度最大为原始字符串的长度
        for k in range(2, len1+1):
            # i的初始位置为0,最后的位置肯定要与len1差k个
            # 不然从i到len1就不足k的长度了
            for i in range(len1-k+1):
                # j也一样 
                for j in range(len1-k+1):
                    # 长度为k的字符串再分割出子串,w为分割线
                    for w in range(1, k):
                        # 第一种情况
                        if dp[i][j][w] and dp[i+w][j+w][k-w]:
                            dp[i][j][k] = True
                            # 满足条件就直接break,表示可以变换,直接进行行下一循环
                            break
                        # 第二种情况
                        if dp[i][j+k-w][w] and dp[i+w][j][k-w]:
                            dp[i][j][k] = True
                            break
        
        return dp[0][0][len1]

当然也有递归

class Solution:
    def isScramble(self, s1: str, s2: str) -> bool:
        # 如果两个字串相同
        if s1 == s2:
            return True
        # 两个子串包含不同的字符
        if sorted(s1) != sorted(s2):
            return False
        for i in range(1, len(s1)):
            # 第一种情况
            if self.isScramble(s1[:i], s2[:i]) and self.isScramble(s1[i:], s2[i:]):
                return True
            # 第二种情况
            if self.isScramble(s1[:i], s2[-i:]) and self.isScramble(s1[i:], s2[:-i]):
                return True
        return False
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值