[LeetCode] Palindrome Permutation II 迴文全排列之二
Given a string s
, return all the palindromic permutations (without duplicates) of it. Return an empty list if no palindromic permutation could be form.
For example:
Given s = "aabb"
, return ["abba", "baab"]
.
Given s = "abc"
, return []
.
Hint:
- If a palindromic permutation exists, we just need to generate the first half of the string.
- To generate all distinct permutations of a (half of) string, use a similar approach from: Permutations II or Next Permutation.
這道題是之前那道Palindrome Permutation的拓展,那道題只是讓判斷存不存在迴文全排列,而這題讓我們返回所有的迴文全排列,此題給了我們充分的提示:如果迴文全排列存在,我們只需要生成前半段字串即可,後面的直接根據前半段得到。那麼我們再進一步思考,由於迴文字串有奇偶兩種情況,偶數迴文串例如abba,可以平均分成前後半段,而奇數迴文串例如abcba,需要分成前中後三段,需要注意的是中間部分只能是一個字元,那麼我們可以分析得出,如果一個字串的迴文字串要存在,那麼奇數個的字元只能有0個或1個,其餘的必須是偶數個,所以我們可以用雜湊表來記錄所有字元的出現個數,然後我們找出出現奇數次數的字元加入mid中,如果有兩個或兩個以上的奇數個數的字元,那麼返回空集,我們對於每個字元,不管其奇偶,都將其個數除以2的個數的字元加入t中,這樣做的原因是如果是偶數個,那麼將其一般加入t中,如果是奇數,如果有1個,那麼除以2是0,不會有字元加入t,如果是3個,那麼除以2是1,取一個加入t。等我們獲得了t之後,t是就是前半段字元,我們對其做全排列,每得到一個全排列,我們加上mid和該全排列的逆序列就是一種所求的迴文字串,這樣我們就可以得到所有的迴文全排列了。在全排序的子函式中有一點需要注意的是,如果我們直接用陣列來儲存結果時,並且t中如果有重複字元的話可能會出現重複項,比如 t = "baa" 的話,那麼最終生成的結果會有重複項,不信可以自己嘗試一下。這裡簡單的說明一下,當 start=0,i=1 時,我們交換後得到 aba,在之後當 start=1,i=2 時,交換後可以得到 aab。但是在之後回到第一層當baa後,當 start=0,i=2 時,交換後又得到了 aab,重複就產生了。那麼其實最簡單當去重複的方法就是將結果res定義成HashSet,利用其去重複的特性,可以保證我們得到的是沒有重複的,參見程式碼如下:
解法一:
class Solution { public: vector<string> generatePalindromes(string s) { unordered_set<string> res; unordered_map<char, int> m; string t = "", mid = ""; for (auto a : s) ++m[a]; for (auto it : m) { if (it.second % 2== 1) mid += it.first; t += string(it.second / 2, it.first); if (mid.size() > 1) return {}; } permute(t, 0, mid, res); return vector<string>(res.begin(), res.end()); } void permute(string &t, int start, string mid, unordered_set<string> &res) { if (start >= t.size()) { res.insert(t + mid + string(t.rbegin(), t.rend())); } for (int i = start; i < t.size(); ++i) { if (i != start && t[i] == t[start]) continue; swap(t[i], t[start]); permute(t, start + 1, mid, res); swap(t[i], t[start]); } } };
下面這種方法和上面的方法很相似,不同之處來於求全排列的方法略有不同,上面那種方法是通過交換字元的位置來生成不同的字串,而下面這種方法是通過加不同的字元來生成全排列字串,參見程式碼如下:
解法二:
class Solution { public: vector<string> generatePalindromes(string s) { vector<string> res; unordered_map<char, int> m; string t = "", mid = ""; for (auto a : s) ++m[a]; for (auto &it : m) { if (it.second % 2 == 1) mid += it.first; it.second /= 2; t += string(it.second, it.first); if (mid.size() > 1) return {}; } permute(t, m, mid, "", res); return res; } void permute(string &t, unordered_map<char, int> &m, string mid, string out, vector<string> &res) { if (out.size() >= t.size()) { res.push_back(out + mid + string(out.rbegin(), out.rend())); return; } for (auto &it : m) { if (it.second > 0) { --it.second; permute(t, m, mid, out + it.first, res); ++it.second; } } } };
在來看一種利用了std提供的next_permutation函式來實現的方法,這樣就大大減輕了我們的工作量,但是這種方法個人感覺算是有些投機取巧了,不知道面試的時候面試官允不允許這樣做,貼上來拓寬一下思路也是好的:
解法三:
class Solution { public: vector<string> generatePalindromes(string s) { vector<string> res; unordered_map<char, int> m; string t = "", mid = ""; for (auto a : s) ++m[a]; for (auto it : m) { if (it.second % 2 == 1) mid += it.first; t += string(it.second / 2, it.first); if (mid.size() > 1) return {}; } sort(t.begin(), t.end()); do { res.push_back(t + mid + string(t.rbegin(), t.rend())); } while (next_permutation(t.begin(), t.end())); return res; } };
類似題目:
參考資料: