1. 程式人生 > >演算法設計與分析--動態規劃(十)

演算法設計與分析--動態規劃(十)

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()];
    }
};