140 Word Break II [Leetcode]

題目內容:

Given a string s and a dictionary of words dict, add spaces in s to construct a sentence where each word is a valid dictionary word.

Return all such possible sentences.

For example, given
s = “catsanddog”,
dict = [“cat”, “cats”, “and”, “sand”, “dog”].

A solution is [“cats and dog”, “cat sand dog”].

解題思路:
首先嚐試簡單的回溯,代碼如下:

class Solution {
public:
    vector<string> wordBreak(string s, unordered_set<string>& wordDict) {
        vector<string> result;
        getSentences(wordDict, result, s, 0, "");
        return result;
    }

    void getSentences(unordered_set<string>& wordDict, vector<string> &sentences, string &s, int index, string temp) {
        int size(s.size());
        if(index == size)
            sentences.push_back(temp.substr(1));

        for(int i = index; i < size; ++i) {
            string str = s.substr(index, i-index+1);
            if(wordDict.find(str) != wordDict.end()) {
                getSentences(wordDict, sentences, s, i+1, temp + " " + str);
            }
        }
    }
};

超時,特例如:
“aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab”
[“a”,”aa”,”aaa”,”aaaa”,”aaaaa”,”aaaaaa”,”aaaaaaa”,”aaaaaaaa”,”aaaaaaaaa”,”aaaaaaaaaa”]
還是需要把中間結果保存起來,使用動態規劃來做。

使用二維數組vector<vector<string>>來存從i到j的在字典中的查找得到的中間結果,首先判斷[0,i]的子串是否在字典中,若有,添加分割方法;其次,再從後向前判斷[j,i]是否在字典中,若在字典中且[0,j]有解,那麼添加方法。
代碼實現如下:

class Solution {
public:
    vector<string> wordBreak(string s, unordered_set<string>& wordDict) {
        int size(s.size());
        vector<vector<string>> sentences;
        sentences.resize(size);

        for(int i = 0; i < size; ++i) {
            string sstr = s.substr(0, i+1);
            if(wordDict.find(sstr) != wordDict.end()) {
                sentences[i].push_back(sstr);
            }
            for(int j = i; j > 0; --j) {
                string tstr = s.substr(j, i-j+1);
                if(wordDict.find(tstr) != wordDict.end() && sentences[j-1].size() != 0) {
                    for(int k = 0; k < sentences[j-1].size(); ++k) {
                        string temp = sentences[j-1][k] + " " + tstr;
                        sentences[i].push_back(temp);
                    }
                }
            }
        }

        return sentences[size-1];
    }
};

和前面的方法一樣,遇到一樣的情況時,會出現超時問題。原因猜想是字符串的拼接是一個比較耗時的操作,而上述代碼求解中間結果需要大量的字符串拼接的操作。

如何把字符串拼接的中間結果操作忽略,直接計算出拼接的結果是下一步提速要解決的問題。值得注意的一點是,我們其實無需把字符串拼接的那麼多中間結果都存起來。因爲字符串是一樣的,不一樣的只是分割的位置,我們可以只把分割的位置記錄下來。

另外,還有一個要考慮的是內存問題。如果把中間結果都疊加得保存起來,可能會需要大量的內存。因此我們修改了動態規劃存儲的中間結果的內容,即只存儲由哪些下標可以跳轉到當前的下標。這樣,在最後通過一次回溯或者棧操作實現的DFS,就可以把最後的結果都保存下來。

代碼如下,運行時間20ms,這裏的回溯使用了遞歸實現,如果用棧應該能更快些。

class Solution {
public:
    vector<string> wordBreak(string s, unordered_set<string>& wordDict) {
        int size(s.size());
        vector<vector<int>> sentences(size);

        for(int i = 0; i < size; ++i) {
            if(wordDict.find(s.substr(0, i+1)) != wordDict.end()) {
                sentences[i].push_back(0);
            }
            for(int j = i; j > 0; --j) {
                if(wordDict.find(s.substr(j, i-j+1)) != wordDict.end() && sentences[j-1].size() != 0) {
                    sentences[i].push_back(j);
                }
            }
        }

        vector<string> result;
        getSentences(sentences, s, result, size-1, "");
        return result;
    }

    void getSentences(vector<vector<int>> &breaks, string &s, vector<string> &output, int index, string mid_result) {
        if(index == -1) {
            if(mid_result != "")
                mid_result.pop_back();
            output.push_back(mid_result);
            return;
        }
        int size(breaks[index].size());
        for(int i = 0; i < size; ++i) {
            string temp = s.substr(breaks[index][i], index-breaks[index][i]+1) + " " + mid_result;
            getSentences(breaks, s, output, breaks[index][i]-1, temp);
        }
    }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章