算法设计与分析:Scramble String(Week 8)

学号:16340008

题目:87. Scramble String


Question:

Given a string s1, we may represent it as a binary tree by partitioning it to two non-empty substrings recursively.

Below is one possible representation of s1 = "great":

    great
   /    \
  gr    eat
 / \    /  \
g   r  e   at
           / \
          a   t

To scramble the string, we may choose any non-leaf node and swap its two children.

For example, if we choose the node "gr" and swap its two children, it produces a scrambled string "rgeat".

    rgeat
   /    \
  rg    eat
 / \    /  \
r   g  e   at
           / \
          a   t

We say that "rgeat" is a scrambled string of "great".

Similarly, if we continue to swap the children of nodes "eat" and "at", it produces a scrambled string "rgtae".

    rgtae
   /    \
  rg    tae
 / \    /  \
r   g  ta  e
       / \
      t   a

We say that "rgtae" is a scrambled string of "great".

Given two strings s1 and s2 of the same length, determine if s2 is a scrambled string of s1.

Example 1:

Input: s1 = "great", s2 = "rgeat"
Output: true

Example 2:

Input: s1 = "abcde", s2 = "caebd"
Output: false

Answer:

题目为Dynamic Programming类。题意如下:给定一个字符串s1,将其以二叉树形式不断分割成两条子串,直到所有叶子节点都是单个字母。问能否通过交换若干个非叶子节点的两个子节点,使所有叶子节点按顺序排列为s2。对于函数f(s1,s2),当s1s2符合上面条件时,f(s1,s2)返回True

S1LS1Rs1的树根的两个子节点,S2LS2Rs2的两个子节点。

我们可以推断,若f(s1,s2)True,则必定

①有S1LS2L使f(S1L,S2L)True 且 有S1RS2R使f(S2L,S2R)True

②有S1LS2R使f(S1L,S2R)True 且 有S2LS1R使f(S2L,S1R)True(即S1L与S1L交换后为①)

因此我们可以把问题变为检查s1s2的子串是否互为scrambled string。这里的动态规划十分有分治算法的影子,可以用递归实现。

算法步骤如下:

  1. 检查s1s2是否相同,相同则返回真。
  2. 检查s1s2是否等长,不等长则返回假。
  3. 检查s1s2所含字母是否相同,这是他们能返回真的前提,不相同则返回假
  4. 执行步骤5与6。
  5. 分别将s1s2分割成两个子串(S1LS2L长度相等,右子串同理),将S1LS2L作为s1s2执行步骤1,将S1RS2R作为s1s2执行步骤1,两次调用的返回值相与后返回
  6. 分别将s1s2分割成两个子串(S1LS2R长度相等,右子串同理),将S1LS2R作为s1s2执行步骤1,将S1RS2L作为s1s2执行步骤1,两次调用的返回值相与后返回
  7. 若步骤5或步骤6的返回值为真,则返回真,否则再次按不同长度分割执行步骤5,6。

得到代码如下(python3):

class Solution:
    def isScramble(self, s1, s2):
        """
        :type s1: str
        :type s2: str
        :rtype: bool
        """
        if (s1 == s2):
            return True
        if (len(s1) != len(s2)):
            return False
        if (sorted(s1) != sorted(s2)):
            return False
        length = len(s1)
        for i in range(length - 1):
            if self.isScramble(s1[: i + 1], s2[: i + 1]) and self.isScramble(s1[i + 1:], s2[i + 1:]):
                return True
            if self.isScramble(s1[: i + 1], s2[length - 1 - i:]) and self.isScramble(s1[i + 1:], s2[: length - 1 - i]):
                return True
        
        return False

本地测试代码:

s1 = "abc"
s2 = "bca"
test = Solution()
print(test.isScramble(s1, s2))

运行结果:

然而这个算法有一个可以优化的点。上面的代码相当于一个多叉树,从根部向下发散,得到返回值则往上层传递。实际上有节点是重复的。因此当一个s1s2符合条件时,我们可以将其添加到一个集合中,然后在函数中添加检查s1s2是否在集合中来避开其子节点的发散递归。

改进代码:

class Solution:
    def isScramble(self, s1, s2):
        """
        :type s1: str
        :type s2: str
        :rtype: bool
        """
        S = set()
        if (s1 == s2):
            return True
        if (len(s1) != len(s2)):
            return False
        if (sorted(s1) != sorted(s2)):
            return False
        if (s1, s2) in S:
            return True
        length = len(s1)
        for i in range(length - 1):
            if self.isScramble(s1[: i + 1], s2[: i + 1]) and self.isScramble(s1[i + 1:], s2[i + 1:]):
                S.add((s1,s2))
                S.add((s2,s1))
                return True
            if self.isScramble(s1[: i + 1], s2[length - 1 - i:]) and self.isScramble(s1[i + 1:], s2[: length - 1 - i]):
                S.add((s1,s2))
                S.add((s2,s1))
                return True
        
        return False

结果如下:

然而这个结果似乎会受网站后台的状况有一定浮动,改进前的代码两次提交,一次能达到50ms一次会去到100ms。我们姑且相信这次代码对运算有改进。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值