0049-Group-Anagrams

Problem Description

Given an array of strings strs, group the anagrams together. You can return the answer in any order.

Solution

核心觀念:互為 anagram 的字串有一個共通不變量——排序後相同,或每個字母的出現次數相同。只要為每個字串算出一個「同組必相同」的 canonical key,用 unordered_map<key, 該組字串> 一趟就能分好組,把「兩兩比對是否 anagram」的 O(n²) 降成 O(n)。

n = 字串數,k = 字串最大長度。

方法一:排序字串當 key — O(n·k·log k)/O(n·k)

最直觀:把每個字串排序後的結果當 key,anagram 排序後必然相同,自然落到同一桶。

// Time: O(n * k log k)
// Space: O(n * k)
vector<vector<string>> groupAnagrams(vector<string> &strs) {
  unordered_map<string, vector<string>> mp;
  for (auto s : strs) {
    auto s2 = s;
    ranges::sort(s2);
    mp[s2].push_back(s);  // 排序後字串當 key
  }
  vector<vector<string>> ans;
  for (auto [k, v] : mp) {
    ans.push_back(v);
  }
  return ans;
}

方法二:字母計數當 key(避開排序)— O(n·k)/O(n·k)

用長度 26 的計數陣列組出 key,省掉每個字串的排序 log 因子,是漸進最優解。

// Time: O(n * k)
// Space: O(n * k)
vector<vector<string>> groupAnagrams(vector<string> &strs) {
  unordered_map<string, vector<string>> mp;
  for (auto s : strs) {
    vector<int> cnt(26);
    for (auto c : s) {
      ++cnt[c - 'a'];
    }
    string key = "|";
    for (auto n : cnt) {
      key += to_string(n) + "|";  // 分隔符不可省
    }
    mp[key].push_back(s);
  }
  vector<vector<string>> ans;
  for (auto [k, v] : mp) {
    ans.push_back(v);
  }
  return ans;
}
Warning

計數 key 一定要加分隔符。若直接把數字接起來,a×1,b×11 會得到 "1","11" 拼成 111,而 a×11,b×1 也是 111 → 兩個不同組被誤判為同組。加 |(或 #)分隔即可消除歧義。

方法三:線性掃描比對(自己的初版,較慢)— O(n²·k)

不用 hash map,維護一個「各組 canonical 字串」清單,每個新字串排序後線性掃描找相符的組。邏輯正確但每個字串都要掃過所有已知組,量級大時容易 TLE。

// Time: O(n^2 * k)
// Space: O(n * k)
vector<vector<string>> groupAnagrams(vector<string> &strs) {
  vector<vector<string>> ans;
  vector<string> keys;  // keys[i] = ans[i] 這組的排序後字串
  for (auto str : strs) {
    auto sorted = str;
    ranges::sort(sorted);
    bool found = false;
    for (int i = 0; i < ans.size(); ++i) {
      if (sorted == keys[i]) {
        ans[i].push_back(str);
        found = true;
        break;
      }
    }
    if (!found) {
      ans.push_back({str});
      keys.push_back(sorted);
    }
  }
  return ans;
}
Tip

三者的差別只在「怎麼找到同組」:方法三線性掃 O(n) 找組、方法一/二用 hash O(1) 找組。key 一旦設計成「同組必相同」,換成 hash 就是自然的優化。