Given a string, we can "shift" each of its letter to its successive letter, for example: "abc" -> "bcd"
. We can keep "shifting" which forms the sequence:
"abc" -> "bcd" -> ... -> "xyz"
Given a list of strings which contains only lowercase alphabets, group all strings that belong to the same shifting sequence.
For example, given: ["abc", "bcd", "acef", "xyz", "az", "ba", "a", "z"]
,
Return:
[ ["abc","bcd","xyz"], ["az","ba"], ["acef"], ["a","z"] ]
Note: For the return value, each inner list's elements must follow the lexicographic order.
這道題讓我們重組偏移字符串,所謂偏移字符串,就是一個字符串的每個字符按照字母順序表偏移相同量得到的另一個字符串,兩者互為偏移字符串,注意相同字符串是偏移字符串的一種特殊情況,因為偏移量為0。現在給了一堆長度不同的字符串,讓把互為偏移字符串的歸並到一起,博主最開始想的是建立字符度和該長度的所有偏移字符串的映射,但是很明顯的錯誤是相同長度的不一定都是偏移字符串,比如 'ab' 和 'ba',所以只能用 HashMap 來建立一個字符串和所有和此字符串是偏移字符串的集合之間的映射,由於題目要求結果是按字母順序的,所以用 multiset 來保存結果,一來可以保存重復字符串,二來可以自動排序。然后博主還寫了一個判斷二個字符串是否互為偏移字符串的函數,注意在比較兩個字母距離時采用了加 26,再對 26 取余的 trick。遍歷給定字符串集,對於遍歷到的字符串,再遍歷哈希表,和每個關鍵字調用 isShifted 函數來比較,如果互為偏移字符串,則加入其對應的字符串集,並標記 flag,最后遍歷完 HashMap,沒有跟任何關鍵字互為偏移,那么就新建一個映射,最后要做的就是把 multiset 轉換為 vector 即可,參見代碼如下:
解法一:
// Correct but complicated class Solution { public: vector<vector<string>> groupStrings(vector<string>& strings) { vector<vector<string> > res; unordered_map<string, multiset<string>> m; for (auto a : strings) { bool b = false; for (auto it = m.begin(); it != m.end(); ++it) { if (isShifted(it->first, a)) { it->second.insert(a); b = true; } } if (!b) m[a] = {a}; } for (auto it = m.begin(); it != m.end(); ++it) { res.push_back(vector<string>(it->second.begin(), it->second.end())); } return res; } bool isShifted(string s1, string s2) { if (s1.size() != s2.size()) return false; int diff = (s1[0] + 26 - s2[0]) % 26; for (int i = 1; i < s1.size(); ++i) { if ((s1[i] + 26 - s2[i]) % 26 != diff) return false; } return true; } };
上面那個方法挺復雜的,其實有更好的方法,網友的智慧無窮啊,上面那個方法的不高效之處在於對於每個遍歷到的字符串,都要和 HashMap 中所有的關鍵字都比較一次,而其實我們可以更加巧妙的利用偏移字符串的特點,那就是字符串的每個字母和首字符的相對距離都是相等的,比如 abc 和 efg 互為偏移,對於 abc 來說,b和a的距離是1,c和a的距離是2,對於 efg 來說,f和e的距離是1,g和e的距離是2。再來看一個例子,az 和 yx,z和a的距離是 25,x和y的距離也是 25 (直接相減是 -1,這就是要加 26 然后取余的原因),那么這樣的話,所有互為偏移的字符串都有個 unique 的距離差,根據這個來建立映射就可以很好的進行單詞分組了,這個思路真實太贊了,參見代碼如下:
解法二:
class Solution { public: vector<vector<string>> groupStrings(vector<string>& strings) { vector<vector<string> > res; unordered_map<string, multiset<string>> m; for (auto a : strings) { string t = ""; for (char c : a) { t += to_string((c + 26 - a[0]) % 26) + ","; } m[t].insert(a); } for (auto it = m.begin(); it != m.end(); ++it) { res.push_back(vector<string>(it->second.begin(), it->second.end())); } return res; } };
Github 同步地址:
https://github.com/grandyang/leetcode/issues/249
類似題目:
參考資料:
https://leetcode.com/problems/group-shifted-strings/
https://leetcode.com/problems/group-shifted-strings/discuss/67442/My-Concise-JAVA-Solution