從leetcode(hard)097說開去

原題鏈接:https://leetcode.com/problems/interleaving-string/?tab=Description
Given s1, s2, s3, find whether s3 is formed by the interleaving of s1 and s2.
For example,
Given:
s1 = “aabcc”,
s2 = “dbbca”,
When s3 = “aadbbcbcac”, return true.
When s3 = “aadbbbaccc”, return false.
題目大意:現有字符串s1,s2,s3,求判斷s3是否是由s1和s2交叉排列組成的。

首先講講我的思路。
最開始,我的思路是:讓s3與s1和s2逐一比對,然後位移,如果s3中的字符無法與s1和s2中任一匹配,輸出false,如果s3直到最後都能完美匹配s1和s2,輸出true。但是這種方法的問題在於,如果s1和s2和s3的某個字符完全相同,那就無法判斷究竟應該選擇s1的字符位移還是選擇s2的字符位移,比如:
s1=”aabcd”, s2=”aaaabcd”,s3=”aaaaaabbccdd”
s3的第一個字符,s1的第一個字符,s2的第一個字符都是相同的,這個時候究竟選擇s1位移還是s2位移,就無從判斷了。
我的解決方法是: 一旦碰到三個字符串相同的字符,就記錄下接下來這個字符分別在每個字符串從此處開始連續出現的次數,根據之後出現的字符和這些次數判斷座標應該如何移動。但是此時我又碰到了另一個問題,如果s1,s2,s3,在移動完一批相同字符以後緊跟着又出現了相同的字符,那就沒辦法了。
所以最後只好用動態規劃,遞歸,雖然pass了,但是是在後百分之一。
代碼如下:

#include<iostream>
#include<string>
using namespace std;

class Solution {
public:
    int min(int a, int b, int c)
    {
        int num = a;
        if (b < num) num = b;
        if (c < num) num = c;
        return num;
    }

    bool isInterleave(string s1, string s2, string s3, int i1 = 0, int i2 = 0, int i3 = 0)
    {
        while (i3<s3.size())
        {
            if (i1 >= s1.size() && i2 >= s2.size()) return false;
            else if (i1 >= s1.size() && s2[i2] != s3[i3]) return false;
            else if (i2 >= s2.size() && s1[i1] != s3[i3]) return false;

            if (s1[i1] == s2[i2] && s1[i1] == s3[i3])
            {
                int count1 = 1, count2 = 1, count3 = 1;
                while (i1 + count1 < s1.size() && s1[i1] == s1[i1 + count1]) ++count1;
                while (i2 + count2 < s2.size() && s2[i2] == s2[i2 + count2]) ++count2;
                while (i3 + count3 < s3.size() && s3[i3] == s3[i3 + count3]) ++count3;
                int count = min(count1, count2, count3);
                if (isInterleave(s1, s2, s3, i1 + count, i2, i3 + count)) return true;
                else if (isInterleave(s1, s2, s3, i1, i2 + count, i3 + count)) return true;
                else return false;
            }
            else if (i1 < s1.size() && s3[i3] == s1[i1])  ++i1;
            else if (i2 < s2.size() && s3[i3] == s2[i2])  ++i2;
            else
            {
                return false;
            }
            ++i3;
        }
        if (i1 == s1.size() && i2 == s2.size())
            return true;
        else
            return false;
    }
};

int main()
{
    Solution a;
    string s1 = "aaabcd";
    string s2 = "aaabcb";
    string s3 = "aaaaababcdbc";
    bool out = a.isInterleave(s1, s2, s3);
    cout << out;
    getchar(); getchar();
    return 0;
}

看了討論區大神的代碼嚇了一跳,這個居然可以用廣搜解決,然而我寫廣搜寫了無數遍根本想不到用這個方法解決這個問題。
大體思路是:把s1,s2分別作爲一個棋盤的行和列,找到從左上到右下的路徑,比如,對s1=”aab”,s2=”abc”來說,棋盤的形態是這樣的。

o--a--o--b--o--c--o
|     |     |     |
a     a     a     a
|     |     |     |
o--a--o--b--o--c--o
|     |     |     |
a     a     a     a
|     |     |     |
o--a--o--b--o--c--o
|     |     |     |
b     b     b     b
|     |     |     |
o--a--o--b--o--c--o

代碼如下:

struct MyPoint {
    int y, x; //y和x是座標,從(0,0)開始
    bool operator==(const MyPoint &p) const {
        return p.y == y && p.x == x;
    }//重載了==運算符
};
namespace std {
    template <>
    struct hash<MyPoint> {
        size_t operator () (const MyPoint &f) const {
            return (std::hash<int>()(f.x) << 1) ^ std::hash<int>()(f.y);
        }
    };
}

class Solution {
public:
    bool isInterleave(string s1, string s2, string s3) {
        if (s1.size() + s2.size() != s3.size()) return false;

        queue<MyPoint> q;
        unordered_set<MyPoint> visited; //用以記錄當前座標是否遍歷的集合
        bool isSuccessful = false;
        int i = 0;

        q.push(MyPoint { 0, 0 });
        q.push(MyPoint { -1, -1 });//這是爲了指示是不是最開始就找不到匹配的點。
        while (!(1 == q.size() && -1 == q.front().x)) {
            auto p = q.front();
            q.pop();
            if (p.y == s1.size() && p.x == s2.size()) {
                return true;
            }
            if (-1 == p.y) {
                q.push(p);
                i++;
                continue;
            }
            if (visited.find(p) != visited.end()) { continue; }
            visited.insert(p);

            if (p.y < s1.size()) { // down
                if (s1[p.y] == s3[i]) { q.push(MyPoint { p.y + 1, p.x }); }
            }
            if (p.x < s2.size()) { // right 
                if (s2[p.x] == s3[i]) { q.push(MyPoint { p.y, p.x + 1 }); }
            }
        }
        return false;
    }
};

另外還有一種效率次一點的DP算法。DP算法的邏輯其實和迪傑斯特拉有異曲同工之妙,它構建了一個bool型的table,table的長和寬分別是s1和s2的size()。對table中每個點來說,它只關心在在左邊和上邊的兩個點的bool值是不是1,如果它們中有一個是1,那麼ok,我們是可以到達這個點的,到了這個點以後再將s1或s2對應的座標與s3對應的座標比較,判斷這個位置究竟是1還是0,其實也是走迷宮,從左上能走到右下,就說明可行。

 bool isInterleave(string s1, string s2, string s3) {

    if(s3.length() != s1.length() + s2.length())
        return false;

    bool table[s1.length()+1][s2.length()+1];

    for(int i=0; i<s1.length()+1; i++)
        for(int j=0; j< s2.length()+1; j++){
            if(i==0 && j==0)
                table[i][j] = true;
            else if(i == 0)
                table[i][j] = ( table[i][j-1] && s2[j-1] == s3[i+j-1]);
            else if(j == 0)
                table[i][j] = ( table[i-1][j] && s1[i-1] == s3[i+j-1]);
            else
                table[i][j] = (table[i-1][j] && s1[i-1] == s3[i+j-1] ) || (table[i][j-1] && s2[j-1] == s3[i+j-1] );
        }

    return table[s1.length()][s2.length()];
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章