1. 程式人生 > >演算法設計與分析:Scramble String(Week 8)

演算法設計與分析:Scramble String(Week 8)

學號:16340008

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。我們姑且相信這次程式碼對運算有改進。