演算法設計與分析--動態規劃(十)
Scramble String
題目
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
分析
法一:
從題目給出的定義來看,該過程是通過樹來定義的,而樹是通過遞迴進行定義的,那麼我們可以大膽地猜測,這個過程可以通過遞迴的方式去解決。在完成驗證之前,我們需要對一些簡單的情況進行排除:
- 長度不一樣的直接排除。
- 相同的字母個數不一樣的直接排除。
- 通過遞迴的方式對其進行驗證。 在用遞迴進行處理的時候,我們需要注意可能出現的情況,首先是根節點下的兩個節點直接做了交換,這種情況,我們驗證的時候需要將源字元的前半段和目標字元的後半段對比,然後遞迴地進行比較。也就是
(isScramble(s1.substr(0, i), s2.substr(size - i, i))&&isScramble(s2.substr(0, size - i), s1.substr(i, size-i)))
另外一種情況是根節點下屬的兩個沒有進行直接的交換,這時字串的前半段是在前半段進行交換,後半段在後半段的範圍內進行交換,情況如下:
isScramble(s1.substr(0, i), s2.substr(0, i))&&isScramble(s1.substr(i), s2.substr(i))
這兩種情況有一種情況成立,我們就認為這個交換是合法的。
法二:
這裡我們選擇用動態規劃思想去解決,定義dp[i][j][len]表示第一個字串下標i開始,第二個字串下標j開始的長度為len並且這個交換是合法的字串,那麼最後結果的表示就是dp[0][0][size],那麼我們求出其子問題即可,首先我們將兩個字串字元相等的對dp進行初始化,然後我們尋找狀態轉移方程,對應一個dp[i][j][len]是否合法,我們有一下三種情況:
- dp[i][j][len]這個問題已經求過並且它是合法的。
- 將這個問題劃分,如果是沒有進行前後交換的情況,有如下狀態轉移方程:
dp[i][j][len] = (dp[i][j][k]&&dp[i+k][j+k][len-k])
- 如果有進行前後交換的情況,狀態轉移方程如下:
dp[i][j][len] = (dp[i][j+len-k][k]&&dp[i+k][j][len-k])
綜合起來,總的狀態轉移方程就是:
dp[i][j][len] = dp[i][j][len] ||
(dp[i][j][k]&&dp[i+k][j+k][len-k]) ||
(dp[i][j+len-k][k]&&dp[i+k][j][len-k]);
原始碼
遞迴實現
class Solution {
public:
bool isScramble(string s1, string s2) {
if(s1 == s2) {
return true;
}
//first base test
if(s1.size() != s2.size()) {
return false;
}
vector<int> count1(26, 0);
vector<int> count2(26, 0);
for(int i = 0; i < s1.size(); i++) {
count1[s1[i] - 'a']++;
count2[s2[i] - 'a']++;
}
for(int i = 0; i < 26; i++) {
if(count1[i] != count2[i]) {
return false;
}
}
int size = s1.size();
for(int i = 1; i < s1.size(); i++) {
if(isScramble(s1.substr(0, i), s2.substr(0, i))&&isScramble(s1.substr(i), s2.substr(i)) ||
(isScramble(s1.substr(0, i), s2.substr(size - i, i))&&isScramble(s2.substr(0, size - i), s1.substr(i, size-i)))) {
return true;
}
}
return false;
}
};
動態規劃實現
class Solution {
public:
bool isScramble(string s1, string s2) {
if(s1.size() == 0) {
return true;
}
vector<vector<vector<bool>>> dp(s1.size(), vector<vector<bool>>(s2.size(), vector<bool>(s1.size()+1, false)));
for(int i = 0; i < s1.size(); i++) {
for(int j = 0; j < s2.size(); j++) {
if(s1[i] == s2[j]) {
dp[i][j][1] = true;
}
}
}
for(int len = 2; len <= s1.size(); len++) {
for(int i = 0; i < s1.size() - len + 1; i++) {
for(int j = 0; j < s2.size() - len + 1; j++) {
for(int k = 1; k < len; k++) {
dp[i][j][len] = dp[i][j][len] ||
(dp[i][j][k]&&dp[i+k][j+k][len-k]) ||
(dp[i][j+len-k][k]&&dp[i+k][j][len-k]);
}
}
}
}
return dp[0][0][s1.size()];
}
};