演算法設計與分析: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"
"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類。題意如下:給定一個字串,將其以二叉樹形式不斷分割成兩條子串,直到所有葉子節點都是單個字母。問能否通過交換若干個非葉子節點的兩個子節點,使所有葉子節點按順序排列為。對於函式,當與符合上面條件時,返回。
設與為的樹根的兩個子節點,與為的兩個子節點。
我們可以推斷,若為,則必定
①有與使為 且 有與使為
或
②有與使為 且 有與使為(即S1L與S1L交換後為①)
因此我們可以把問題變為檢查與的子串是否互為scrambled string。這裡的動態規劃十分有分治演算法的影子,可以用遞迴實現。
演算法步驟如下:
- 檢查與是否相同,相同則返回真。
- 檢查與是否等長,不等長則返回假。
- 檢查與所含字母是否相同,這是他們能返回真的前提,不相同則返回假
- 執行步驟5與6。
- 分別將與分割成兩個子串(與長度相等,右子串同理),將與作為與執行步驟1,將與作為與執行步驟1,兩次呼叫的返回值相與後返回
- 分別將與分割成兩個子串(與長度相等,右子串同理),將與作為與執行步驟1,將與作為與執行步驟1,兩次呼叫的返回值相與後返回
- 若步驟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))
執行結果:
然而這個演算法有一個可以優化的點。上面的程式碼相當於一個多叉樹,從根部向下發散,得到返回值則往上層傳遞。實際上有節點是重複的。因此當一個與符合條件時,我們可以將其新增到一個集合中,然後在函式中新增檢查與是否在集合中來避開其子節點的發散遞迴。
改進程式碼:
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。我們姑且相信這次程式碼對運算有改進。