Skip to content

Group Anagrams

Hash map grouping with canonical representation

Group Anagrams is a classic medium-difficulty problem that tests your understanding of hash maps, string manipulation, and the concept of canonical forms for grouping related items.


Problem Statement

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

An anagram is a word or phrase formed by rearranging the letters of a different word or phrase, using all the original letters exactly once.

This is LeetCode Problem #49 - a Medium difficulty problem.

Examples

InputOutput
["eat","tea","tan","ate","nat","bat"][["bat"],["nat","tan"],["ate","eat","tea"]]
[""][[""]]
["a"][["a"]]

Constraints

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

Core Insight

Two strings are anagrams if and only if they have the same characters with the same frequencies. We need a way to identify anagrams that can serve as a hash map key.

Canonical Forms for Anagrams

ApproachKey for "eat"Key for "tea"Same?
Sorted string"aet""aet"Yes
Character count tuple(1,0,0,0,1,0,...,1,0)(1,0,0,0,1,0,...,1,0)Yes
Character count string"a1e1t1""a1e1t1"Yes

Approach 1: Sorted String as Key (Most Common)

How It Works

  1. For each string, sort its characters to get a canonical form
  2. Use the sorted string as a key in a hash map
  3. Group strings with the same key

Mermaid Diagram

Visual Walkthrough

Input: ["eat", "tea", "tan", "ate", "nat", "bat"]

String    Sorted Key    Added to Group
------    ----------    --------------
"eat"     "aet"         groups["aet"] = ["eat"]
"tea"     "aet"         groups["aet"] = ["eat", "tea"]
"tan"     "ant"         groups["ant"] = ["tan"]
"ate"     "aet"         groups["aet"] = ["eat", "tea", "ate"]
"nat"     "ant"         groups["ant"] = ["tan", "nat"]
"bat"     "abt"         groups["abt"] = ["bat"]

Result: [["eat", "tea", "ate"], ["tan", "nat"], ["bat"]]

Solution

python
from collections import defaultdict

def groupAnagrams(strs: list[str]) -> list[list[str]]:
    """
    Group anagrams using sorted string as key.

    Args:
        strs: List of strings to group

    Returns:
        List of groups, where each group contains anagrams

    Time: O(n * k log k) where n = number of strings, k = max string length
    Space: O(n * k) for storing all strings in the hash map
    """
    groups = defaultdict(list)

    for s in strs:
        # Sort characters to get canonical form
        key = ''.join(sorted(s))
        groups[key].append(s)

    return list(groups.values())
java
public List<List<String>> groupAnagrams(String[] strs) {
    Map<String, List<String>> groups = new HashMap<>();
    for (String s : strs) {
        char[] ch = s.toCharArray();
        Arrays.sort(ch);
        String key = new String(ch);
        groups.computeIfAbsent(key, k -> new ArrayList<>()).add(s);
    }
    return new ArrayList<>(groups.values());
}

Complexity: Time O(n * k log k) - Space O(n * k)

  • Time: O(n * k log k) where n is number of strings and k is max string length. Sorting each string takes O(k log k).
  • Space: O(n * k) to store all strings in the hash map grouped by their sorted key.

Using tuple as Key (Slightly Faster)

python
def groupAnagrams_tuple_key(strs: list[str]) -> list[list[str]]:
    """
    Use tuple(sorted(s)) as key - avoids string join.
    """
    groups = defaultdict(list)

    for s in strs:
        key = tuple(sorted(s))
        groups[key].append(s)

    return list(groups.values())

Complexity: Time O(n * k log k) - Space O(n * k)

  • Time: O(n * k log k) - same as string key version but avoids string join operation.
  • Space: O(n * k) for storing strings. Tuple keys have slightly lower overhead than string keys.

Approach 2: Character Count as Key

How It Works

Instead of sorting (O(k log k) per string), count character frequencies (O(k) per string).

Use the count as a tuple key: (count_a, count_b, ..., count_z)

Solution

python
def groupAnagrams_count(strs: list[str]) -> list[list[str]]:
    """
    Group anagrams using character count tuple as key.

    Time: O(n * k) where n = number of strings, k = max string length
    Space: O(n * k) for storing strings
    """
    groups = defaultdict(list)

    for s in strs:
        # Count characters - O(k)
        count = [0] * 26
        for char in s:
            count[ord(char) - ord('a')] += 1

        # Use tuple as key (lists aren't hashable)
        key = tuple(count)
        groups[key].append(s)

    return list(groups.values())

Complexity: Time O(n * k) - Space O(n * k)

  • Time: O(n * k) where counting characters is O(k) per string - faster than O(k log k) sorting for longer strings.
  • Space: O(n * k) for storing strings plus O(n * 26) for the count tuple keys (constant factor).

Visual Walkthrough

String "eat":
  count = [1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0]
  #        a       e                             t
  key = (1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0)

String "tea":
  count = [1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0]
  key = (1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0)

Same key! -> Grouped together

Complexity Comparison

ApproachTimeSpaceNotes
Sorted KeyO(n * k log k)O(n * k)Simple to implement
Count KeyO(n * k)O(n * k)Faster for long strings

Where:

  • n = number of strings
  • k = maximum length of a string

When to use which:

  • For short strings (k < 10): Sorted key is fine
  • For long strings: Character count is better
  • For interviews: Either works, sorted is cleaner to write

Alternative Implementations

Using Counter as Key (Not Directly Possible)

python
from collections import Counter

def groupAnagrams_counter(strs: list[str]) -> list[list[str]]:
    """
    Counter isn't hashable, so we convert to frozenset of items.

    Note: This works but is less common in interviews.
    """
    groups = defaultdict(list)

    for s in strs:
        # Convert Counter to hashable key
        key = frozenset(Counter(s).items())
        groups[key].append(s)

    return list(groups.values())

Complexity: Time O(n * k) - Space O(n * k)

  • Time: O(n * k) - Counter creation is O(k), frozenset creation is O(unique chars).
  • Space: O(n * k) for strings. Frozenset keys are more compact for sparse character distributions.

One-liner (Pythonic but Less Readable)

python
from itertools import groupby

def groupAnagrams_oneliner(strs: list[str]) -> list[list[str]]:
    """
    Pythonic one-liner using groupby.

    Note: Requires sorting first, so O(n log n * k) overall.
    Not recommended for interviews - hard to explain.
    """
    sorted_strs = sorted(strs, key=sorted)
    return [list(group) for _, group in groupby(sorted_strs, key=sorted)]

Complexity: Time O(n log n * k log k) - Space O(n * k)

  • Time: O(n log n * k log k) - sorting n strings requires n log n comparisons, each comparison sorts strings in O(k log k).
  • Space: O(n * k) for the sorted list copy and grouped results.

Edge Cases

CaseInputOutputNote
Empty strings["", "", ""][["", "", ""]]All are anagrams of each other
Single string["abc"][["abc"]]One group with one element
All different["a", "b", "c"][["a"], ["b"], ["c"]]Each in own group
All same["abc", "abc"][["abc", "abc"]]Identical strings are anagrams
Mixed lengths["a", "ab", "ba"][["a"], ["ab", "ba"]]Different lengths can't be anagrams

Interview Tips

  1. Start with sorted key approach: It's cleaner and easier to explain
  2. Mention count approach: Shows you know the optimization
  3. Use defaultdict: Cleaner than checking if key exists
  4. Handle empty string: It's a valid input with key "" or empty count

Common Follow-up Questions

  • "Can you optimize time?" - Yes, use character count (O(k) vs O(k log k))
  • "What if strings are very long?" - Count approach is better
  • "What about Unicode?" - Use dict-based count instead of array

Valid Anagram (LeetCode 242)

Problem: Given two strings s and t, return true if t is an anagram of s, and false otherwise.

Key Insight: Two strings are anagrams if they have identical character frequency counts.

Approach: Compare character counts using Counter or a 26-element array. For O(1) space with lowercase letters, use array-based counting.

Complexity: O(n) time, O(1) space (26 letters)

Find All Anagrams in a String (LeetCode 438)

Problem: Given strings s and p, return all start indices of p's anagrams in s.

Key Insight: Combine anagram checking with sliding window technique.

Approach: Use fixed-size sliding window of length p. Maintain frequency counts and compare as window slides. Can use array for O(1) comparisons.

Complexity: O(n) time, O(1) space

Group Shifted Strings (LeetCode 249)

Problem: Given an array of strings, group strings that can be shifted to become each other. Shifting means incrementing each character (with wrap-around).

Key Insight: Strings in the same group have the same "difference signature" between consecutive characters.

Approach: For each string, compute a canonical key based on character differences (modulo 26). Group strings with the same key.

Complexity: O(n * k) time where k is max string length, O(n * k) space


Complete Solution with Tests

python
from collections import defaultdict

def groupAnagrams(strs: list[str]) -> list[list[str]]:
    """
    Group anagrams together using sorted string as key.

    Time: O(n * k log k)
    Space: O(n * k)
    """
    groups = defaultdict(list)

    for s in strs:
        key = ''.join(sorted(s))
        groups[key].append(s)

    return list(groups.values())

Complexity: Time O(n * k log k) - Space O(n * k)

  • Time: O(n * k log k) where n is number of strings, k is max string length. Each string is sorted once.
  • Space: O(n * k) for the hash map storing all grouped strings.
python
# Test cases
if __name__ == "__main__":
    # Test 1: Standard case
    result = groupAnagrams(["eat", "tea", "tan", "ate", "nat", "bat"])
    print("Test 1:", result)
    # Expected: [["eat","tea","ate"], ["tan","nat"], ["bat"]] (order may vary)

    # Test 2: Empty string
    result = groupAnagrams([""])
    print("Test 2:", result)
    # Expected: [[""]]

    # Test 3: Single character
    result = groupAnagrams(["a"])
    print("Test 3:", result)
    # Expected: [["a"]]

    # Test 4: Multiple empty strings
    result = groupAnagrams(["", "", ""])
    print("Test 4:", result)
    # Expected: [["", "", ""]]

    print("All tests passed!")

Summary

Key PointDetails
Core TechniqueUse canonical form as hash key
Best Key TypeSorted string or character count tuple
Time ComplexityO(n * k log k) or O(n * k)
Space ComplexityO(n * k)

Takeaway: Group Anagrams is a canonical "grouping by key" problem. The key insight is that anagrams share the same sorted form. Master this pattern as it applies to many similar grouping problems.


References