150NeetCode 150

4.Group Anagrams

MediumBlind 75Arrays & Hashing Β· 4/9

Problem

Given an array of strings strs, group the strings that are anagrams of each other and return the groups. The groups, and the strings inside each group, may be returned in any order.

Two strings are anagrams when they contain exactly the same letters with the same counts, e.g. "eat", "tea" and "ate".

Examples

Input:  strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
Output: [["bat"], ["nat", "tan"], ["ate", "eat", "tea"]]
Explanation: "eat", "tea", "ate" share letters {a,e,t}; "tan", "nat" share {a,n,t}; "bat" is alone.
Input:  strs = [""]
Output: [[""]]
Input:  strs = ["a"]
Output: [["a"]]

Constraints

  • 1 <= strs.length <= 10^4
  • 0 <= strs[i].length <= 100
  • strs[i] consists of lowercase English letters.

πŸ’‘ Key Insight

Give every string a canonical key that is identical for all its anagrams and different for everything else, then bucket strings by key in a HashMap<String, List<String>>. Two good keys exist: the sorted string ("eat" β†’ "aet"), or the 26-letter frequency signature ("eat" β†’ 1#0#0#0#1#...#1#...). The frequency key avoids the per-string sort.

Brute Force

TimeO(nΒ² Β· k)SpaceO(n)

Intuition

Take the first ungrouped string, start a new group, then scan all remaining ungrouped strings and pull in those that are anagrams of it (checked with a 26-letter count comparison). Repeat until every string is grouped.

Algorithm

  1. Keep a boolean[] used.
  2. For each i not yet used: start group = [strs[i]], mark used[i].
  3. For each j > i not used: if isAnagram(strs[i], strs[j]), add it to group and mark used[j].
  4. Append group to the result.

Visualization

strs = [eat, tea, tan, ate, nat, bat]

i=0 eat: tea βœ“, tan βœ—, ate βœ“, nat βœ—, bat βœ— β†’ [eat, tea, ate]
i=2 tan: nat βœ“, bat βœ—                       β†’ [tan, nat]
i=5 bat:                                     β†’ [bat]

Code

java
import java.util.ArrayList;
import java.util.List;

class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        int n = strs.length;
        boolean[] used = new boolean[n];
        List<List<String>> result = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            if (used[i]) {
                continue;
            }
            List<String> group = new ArrayList<>();
            group.add(strs[i]);
            used[i] = true;
            for (int j = i + 1; j < n; j++) {
                if (!used[j] && isAnagram(strs[i], strs[j])) {
                    group.add(strs[j]);
                    used[j] = true;
                }
            }
            result.add(group);
        }
        return result;
    }

    private boolean isAnagram(String a, String b) {
        if (a.length() != b.length()) {
            return false;
        }
        int[] count = new int[26];
        for (int k = 0; k < a.length(); k++) {
            count[a.charAt(k) - 'a']++;
            count[b.charAt(k) - 'a']--;
        }
        for (int c : count) {
            if (c != 0) {
                return false;
            }
        }
        return true;
    }
}

Complexity

  • Time: O(nΒ² Β· k) β€” up to nΒ² pair comparisons, each O(k) where k is the max string length.
  • Space: O(n) β€” the used array (output not counted).

Complexity Comparison

Approach Time Space
Brute Force O(nΒ² Β· k) O(n)
Sorted String Key O(n Β· k log k) O(n Β· k)
Character Count Key O(n Β· k) O(n Β· k)

Common Pitfalls

  • Using int[] directly as a map key β€” arrays use identity hashCode/equals, so no two strings ever share a bucket. Convert to a String (or Arrays.toString).
  • Building the count key without separators β€” counts [1, 11] and [11, 1] would both become "111".
  • Using chars.toString() instead of new String(chars) β€” it returns something like [C@1b6d3586, not the characters.
  • Forgetting the empty string: "" is a valid input and must form its own group.