[LeetCode] 425. Word Squares 單詞平方


 

Given a set of words (without duplicates), find all word squares you can build from them.

A sequence of words forms a valid word square if the kth row and column read the exact same string, where 0 ≤ k < max(numRows, numColumns).

For example, the word sequence ["ball","area","lead","lady"] forms a word square because each word reads the same both horizontally and vertically.

b a l l
a r e a
l e a d
l a d y

Note:

  1. There are at least 1 and at most 1000 words.
  2. All words will have the exact same length.
  3. Word length is at least 1 and at most 5.
  4. Each word contains only lowercase English alphabet a-z.

 

Example 1:

Input:
["area","lead","wall","lady","ball"]

Output:
[
  [ "wall",
    "area",
    "lead",
    "lady"
  ],
  [ "ball",
    "area",
    "lead",
    "lady"
  ]
]

Explanation:
The output consists of two word squares. The order of output does not matter (just the order of words in each word square matters).

 

Example 2:

Input:
["abat","baba","atan","atal"]

Output:
[
  [ "baba",
    "abat",
    "baba",
    "atan"
  ],
  [ "baba",
    "abat",
    "baba",
    "atal"
  ]
]

Explanation:
The output consists of two word squares. The order of output does not matter (just the order of words in each word square matters).

 

這道題是之前那道 Valid Word Square 的延伸,由於要求出所有滿足要求的單詞平方,所以難度大大的增加了,不要幻想着可以利用之前那題的解法來暴力破解,OJ 不會答應的。那么根據以往的經驗,對於這種要打印出所有情況的題的解法大多都是用遞歸來解,那么這題的關鍵是根據前綴來找單詞,如果能利用合適的數據結構來建立前綴跟單詞之間的映射,使得我們能快速的通過前綴來判斷某個單詞是否存在,這是解題的關鍵。對於建立這種映射,這里主要有兩種方法,一種是利用 HashMap 來建立前綴和所有包含此前綴單詞的集合之前的映射,第二種方法是建立前綴樹 Trie,顧名思義,前綴樹專門就是為這種問題設計的。首先來看第一種方法,用 HashMap 來建立映射的方法,就是取出每個單詞的所有前綴,然后將該單詞加入該前綴對應的集合中去,然后建立一個空的 nxn 的 char 矩陣,其中n為單詞的長度,目標就是來把這個矩陣填滿,從0開始遍歷,先取出長度為0的前綴,即空字符串,由於在建立映射的時候,空字符串也和每個單詞的集合建立了映射,然后遍歷這個集合,用遍歷到的單詞的i位置字符,填充矩陣 mat[i][i],然后j從 i+1 出開始遍歷,對應填充矩陣 mat[i][j] 和 mat[j][i],然后根據第j行填充得到的前綴,到哈希表中查看有沒單詞,如果沒有,就 break 掉,如果有,則繼續填充下一個位置。最后如果 j==n 了,說明第0行和第0列都被填好了,再調用遞歸函數,開始填充第一行和第一列,依次類推,直至填充完成,參見代碼如下:

 

解法一:

class Solution {
public:
    vector<vector<string>> wordSquares(vector<string>& words) {
        vector<vector<string>> res;
        unordered_map<string, set<string>> m;
        int n = words[0].size();
        for (string word : words) {
            for (int i = 0; i < n; ++i) {
                string key = word.substr(0, i);
                m[key].insert(word);
            }
        }
        vector<vector<char>> mat(n, vector<char>(n));
        helper(0, n, mat, m, res);
        return res;
    }
      void helper(int i, int n, vector<vector<char>>& mat, unordered_map<string, set<string>>& m, vector<vector<string>>& res) {
            if (i == n) {
                vector<string> out;
                for (int j = 0; j < n; ++j) out.push_back(string(mat[j].begin(), mat[j].end()));
                res.push_back(out);
                return;
            }
            string key = string(mat[i].begin(), mat[i].begin() + i);
        for (string str : m[key]) { 
            mat[i][i] = str[i];
            int j = i + 1;
            for (; j < n; ++j) {
                mat[i][j] = str[j];
                mat[j][i] = str[j];
                if (!m.count(string(mat[j].begin(), mat[j].begin() + i + 1))) break;
            }
            if (j == n) helper(i + 1, n, mat, m, res);
        }
    }
};

 

下面來看建立前綴樹 Trie 的方法,這種方法的難點是看能不能熟練的寫出 Trie 的定義,還有構建過程,以及后面在遞歸函數中,如果利用前綴樹來快速查找單詞的前綴,總之,這道題是前綴樹的一種經典的應用,能白板寫出來就說明基本上已經掌握了前綴樹了,參見代碼如下:

 

解法二:

class Solution {
public:
    struct TrieNode {
        vector<int> indexs;
        vector<TrieNode*> children;
        TrieNode(): children(26, nullptr) {}
    };
    TrieNode* buildTrie(vector<string>& words) {
        TrieNode *root = new TrieNode();
        for (int i = 0; i < words.size(); ++i) {
            TrieNode *t = root;
            for (int j = 0; j < words[i].size(); ++j) {
                if (!t->children[words[i][j] - 'a']) {
                    t->children[words[i][j] - 'a'] = new TrieNode();
                }
                t = t->children[words[i][j] - 'a'];
                t->indexs.push_back(i);
            }
        }
        return root;
    }
    vector<vector<string>> wordSquares(vector<string>& words) {
        TrieNode *root = buildTrie(words);
        vector<string> out(words[0].size());
        vector<vector<string>> res;
        for (string word : words) {
            out[0] = word;
            helper(words, 1, root, out, res);
        }
        return res;
    }
    void helper(vector<string>& words, int level, TrieNode* root, vector<string>& out, vector<vector<string>>& res) {
        if (level >= words[0].size()) {
            res.push_back(out);
            return;
        }
        string str = "";
        for (int i = 0; i < level; ++i) {
            str += out[i][level];
        }
        TrieNode *t = root;
        for (int i = 0; i < str.size(); ++i) {
            if (!t->children[str[i] - 'a']) return;
            t = t->children[str[i] - 'a'];
        }
        for (int idx : t->indexs) {
            out[level] = words[idx];
            helper(words, level + 1, root, out, res);
        }
    }
};

 

Github 同步地址:

https://github.com/grandyang/leetcode/issues/425

 

類似題目:

Valid Word Square

 

參考資料:

https://leetcode.com/problems/word-squares/

https://leetcode.com/problems/word-squares/discuss/91380/java-53ms-dfs-hashmap

https://leetcode.com/problems/word-squares/discuss/91344/Short-PythonC%2B%2B-solution

https://leetcode.com/problems/word-squares/discuss/91333/Explained.-My-Java-solution-using-Trie-126ms-1616

https://leetcode.com/problems/word-squares/discuss/91337/70ms-Concise-C%2B%2B-Solution-Using-Trie-and-Backtracking

 

LeetCode All in One 題目講解匯總(持續更新中...)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM