Katkat

Anagram Group Tracker with Add, Remove, and Largest-Group Queries: Read vs Write Trade-offs

CoupangSoftware Engineer · Onsite

Last updated

Design an in-memory data structure that supports adding words, removing words, and querying the largest anagram group. Tests canonical key generation, dynamic maximum maintenance, and read-versus-write algorithmic trade-offs.

Problem Statement

Design an in-memory data structure, AnagramGroupTracker, that stores a collection of words and dynamically tracks groups of anagrams. Two words are anagrams if they contain the exact same characters with the same frequencies (e.g., "eat", "tea", and "ate").

The structure must support three operations using only standard library data structures:

  1. add_word(word: str) -> None: Inserts word into the collection. Duplicate identical words are allowed, and each instance counts toward its anagram group size.
  2. remove_word(word: str) -> bool: Removes a single occurrence of word from the collection if it exists. Returns True if the word was found and removed, or False if it was not present.
  3. get_largest_group() -> list[str]: Returns a list of all words currently belonging to the largest anagram group. If multiple anagram groups tie for the largest size, returning any one of them is acceptable. If the collection is empty, return an empty list [].

Be prepared to discuss read-optimized vs. write-optimized implementations and how your design choices adapt to read-heavy versus write-heavy workloads.

Interface

class AnagramGroupTracker:
    def __init__(self):
        pass

    def add_word(self, word: str) -> None:
        pass

    def remove_word(self, word: str) -> bool:
        pass

    def get_largest_group(self) -> list[str]:
        pass

Example

tracker = AnagramGroupTracker()
tracker.add_word("cat")
tracker.add_word("act")
tracker.add_word("dog")
tracker.get_largest_group()  # Returns ["cat", "act"] (or any permutation)

tracker.add_word("god")
tracker.add_word("good")
tracker.add_word("dog")
# Anagram groups:
# "act": ["cat", "act"] (size 2)
# "dgo": ["dog", "god", "dog"] (size 3)
# "dgoo": ["good"] (size 1)
tracker.get_largest_group()  # Returns ["dog", "god", "dog"]

tracker.remove_word("dog")
tracker.remove_word("dog")
# Now "dgo" group has only ["god"] (size 1)
tracker.get_largest_group()  # Returns ["cat", "act"]

tracker.remove_word("bird")  # Returns False

Constraints

  • All words consist of lowercase English letters ('a' - 'z').
  • 1 <= len(word) <= 100
  • Total number of operations (add_word, remove_word, get_largest_group) <= 100,000.
  • Order of words in the returned group does not matter.

Edge Cases

  • Calling get_largest_group() on an empty tracker returns [].
  • Adding and removing identical strings multiple times.
  • Removing a word that reduces the largest group size, requiring the tracker to fall back to the next largest group.

Hints

Hint 1

To identify anagrams in O(L) or O(L log L) time, represent each word by a canonical key, such as sorted characters or a 26-element character count tuple.

Hint 2

For a write-optimized approach, simply maintain a hash map of canonical key to word occurrences and iterate through all groups on `get_largest_group()`.

Hint 3

For a read-optimized approach, maintain group sizes using frequency buckets (similar to an LFU cache) or a max-heap with lazy deletion so that `get_largest_group()` runs in O(1) or O(log K) time.

Follow-up questions

  • How would you adapt the canonical key generation if words can contain arbitrary Unicode characters instead of lowercase ASCII?
  • If write operations heavily outnumber read queries (99% writes, 1% reads), what is the optimal time complexity for each method?
  • How would you ensure thread safety if multiple reader threads call `get_largest_group()` while writer threads concurrently call `add_word()` and `remove_word()`?