Facebook Pixel

288. Unique Word Abbreviation 🔒

MediumDesignArrayHash TableString
Leetcode Link

Problem Description

This problem asks you to implement a class that checks if a word's abbreviation is unique within a dictionary.

Abbreviation Rules:

  • For words with 3 or more characters: the abbreviation is the first letter + the count of middle characters + the last letter
    • Example: "dog" becomes "d1g" (1 character between 'd' and 'g')
    • Example: "internationalization" becomes "i18n" (18 characters between 'i' and 'n')
  • For words with exactly 2 characters: the word is its own abbreviation
    • Example: "it" remains "it"

Class Implementation:

You need to implement the ValidWordAbbr class with two methods:

  1. __init__(dictionary): Initialize the object with an array of words
  2. isUnique(word): Returns true if the word is unique, false otherwise

Uniqueness Definition:

A word is considered unique if ONE of these conditions is true:

  • No word in the dictionary has the same abbreviation as the given word
  • All words in the dictionary that share the same abbreviation are exactly the same as the given word

For example, if the dictionary contains ["deer", "door", "cake", "card"]:

  • isUnique("dear") returns false because "deer" from the dictionary has the same abbreviation "d2r"
  • isUnique("cart") returns true because although "card" has abbreviation "c2d", "cart" has abbreviation "c2t" which doesn't match
  • isUnique("cane") returns false because both "cake" and "cane" have abbreviation "c2e" but they are different words
  • isUnique("make") returns true because no word in dictionary has abbreviation "m2e"
  • isUnique("cake") returns true because although "cake" in dictionary has abbreviation "c2e", it's the same word
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that we need to quickly check if a word's abbreviation conflicts with words in our dictionary. Since we'll be making multiple queries, we should preprocess the dictionary to make lookups efficient.

Think about what we're really checking: given a word, we want to know if its abbreviation is "unique" according to the specific rules. The word is unique if either:

  1. No dictionary word shares its abbreviation, OR
  2. All dictionary words that share its abbreviation are identical to the query word itself

This suggests we should group dictionary words by their abbreviations. A hash table is perfect for this - we can use the abbreviation as the key and store all words with that abbreviation as the value.

But what data structure should we use for the value? Since we need to:

  • Check if all words with a given abbreviation are the same
  • Avoid duplicate entries if the same word appears multiple times in the dictionary

A set is ideal. It automatically handles duplicates and makes it easy to check if all elements are identical to our query word.

During initialization, we compute each word's abbreviation and add it to our hash table. For the abbreviation function, we handle the special case where words with less than 3 characters are their own abbreviation.

When checking if a word is unique, we:

  1. Compute its abbreviation
  2. If that abbreviation isn't in our hash table, the word is unique
  3. If it is in our hash table, we check if all words in that set are identical to our query word

This approach gives us O(1) average time complexity for lookups after an O(n) preprocessing step, where n is the size of the dictionary.

Solution Approach

We implement the solution using a hash table with sets as values to efficiently group words by their abbreviations.

Helper Function - abbr(s): First, we create a helper function to compute abbreviations:

  • If the word length is less than 3, return the word itself
  • Otherwise, return s[0] + str(len(s) - 2) + s[-1]

Initialization - __init__(dictionary):

  1. Create a defaultdict(set) called d to store our mapping
  2. Iterate through each word s in the dictionary
  3. Calculate the abbreviation using self.abbr(s)
  4. Add the word to the set at d[abbreviation]

Using a defaultdict(set) ensures:

  • No key errors when accessing non-existent abbreviations
  • Automatic deduplication if the same word appears multiple times
  • Easy grouping of all words with the same abbreviation

Uniqueness Check - isUnique(word):

  1. Calculate the abbreviation of the query word: s = self.abbr(word)
  2. Check if this abbreviation exists in our hash table
  3. Return true if either:
    • The abbreviation s is not in d (no conflicts), OR
    • All words in d[s] are identical to word (checked using all(word == t for t in self.d[s]))

The expression all(word == t for t in self.d[s]) efficiently verifies that every word in the set with abbreviation s is exactly the same as our query word. This handles the case where the word itself is in the dictionary.

Time Complexity:

  • Initialization: O(n) where n is the dictionary size
  • isUnique: O(m) where m is the number of words with the same abbreviation (usually small)

Space Complexity: O(n) to store the dictionary words in the hash table

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small example with dictionary ["deer", "door", "cake", "card"].

Step 1: Initialization

We create our hash table d and process each word:

  1. "deer" → abbreviation: "d2r" (d + 2 middle chars + r)

    • d["d2r"] = {"deer"}
  2. "door" → abbreviation: "d2r" (d + 2 middle chars + r)

    • d["d2r"] = {"deer", "door"}
  3. "cake" → abbreviation: "c2e" (c + 2 middle chars + e)

    • d["c2e"] = {"cake"}
  4. "card" → abbreviation: "c2d" (c + 2 middle chars + d)

    • d["c2d"] = {"card"}

Final hash table:

d = {
    "d2r": {"deer", "door"},
    "c2e": {"cake"},
    "c2d": {"card"}
}

Step 2: Query Examples

Query 1: isUnique("dear")

  • Abbreviation of "dear" = "d2r"
  • Check: Is "d2r" in d? Yes, it maps to {"deer", "door"}
  • Check: Are all words in {"deer", "door"} equal to "dear"? No
  • Return: false

Query 2: isUnique("cart")

  • Abbreviation of "cart" = "c2t"
  • Check: Is "c2t" in d? No
  • Return: true (no conflicts)

Query 3: isUnique("cake")

  • Abbreviation of "cake" = "c2e"
  • Check: Is "c2e" in d? Yes, it maps to {"cake"}
  • Check: Are all words in {"cake"} equal to "cake"? Yes
  • Return: true (all words with this abbreviation are identical to query)

Query 4: isUnique("door")

  • Abbreviation of "door" = "d2r"
  • Check: Is "d2r" in d? Yes, it maps to {"deer", "door"}
  • Check: Are all words in {"deer", "door"} equal to "door"? No ("deer""door")
  • Return: false

This walkthrough demonstrates how the hash table efficiently groups words by abbreviation and how the uniqueness check correctly handles both cases: when no conflicts exist and when all conflicting words are identical to the query.

Solution Implementation

1from collections import defaultdict
2from typing import List
3
4class ValidWordAbbr:
5    def __init__(self, dictionary: List[str]):
6        """
7        Initialize the data structure with a dictionary of words.
8        Store words grouped by their abbreviation.
9      
10        Args:
11            dictionary: List of words to initialize the data structure
12        """
13        # Dictionary to store sets of words grouped by their abbreviation
14        self.abbreviation_map = defaultdict(set)
15      
16        # Build the abbreviation map from the dictionary
17        for word in dictionary:
18            abbreviation = self._get_abbreviation(word)
19            self.abbreviation_map[abbreviation].add(word)
20
21    def isUnique(self, word: str) -> bool:
22        """
23        Check if a word's abbreviation is unique in the dictionary.
24        A word's abbreviation is unique if:
25        1. No word in the dictionary has the same abbreviation, OR
26        2. All words with the same abbreviation are the same as the input word
27      
28        Args:
29            word: The word to check for uniqueness
30          
31        Returns:
32            True if the word's abbreviation is unique, False otherwise
33        """
34        abbreviation = self._get_abbreviation(word)
35      
36        # Check if abbreviation doesn't exist or all words with this abbreviation are the same as input
37        return (abbreviation not in self.abbreviation_map or 
38                all(existing_word == word for existing_word in self.abbreviation_map[abbreviation]))
39
40    def _get_abbreviation(self, word: str) -> str:
41        """
42        Generate abbreviation for a word.
43        Rules:
44        - If word length < 3, the abbreviation is the word itself
45        - Otherwise, abbreviation is: first_letter + count_of_middle_letters + last_letter
46      
47        Args:
48            word: The word to abbreviate
49          
50        Returns:
51            The abbreviation of the word
52        """
53        if len(word) < 3:
54            return word
55      
56        # Create abbreviation: first char + middle count + last char
57        middle_count = len(word) - 2
58        return f"{word[0]}{middle_count}{word[-1]}"
59
60
61# Your ValidWordAbbr object will be instantiated and called as such:
62# obj = ValidWordAbbr(dictionary)
63# param_1 = obj.isUnique(word)
64
1class ValidWordAbbr {
2    // Map to store abbreviation as key and set of original words as value
3    private Map<String, Set<String>> abbreviationMap = new HashMap<>();
4
5    /**
6     * Constructor to initialize the data structure with a dictionary
7     * @param dictionary Array of words to be stored
8     */
9    public ValidWordAbbr(String[] dictionary) {
10        // Process each word in the dictionary
11        for (String word : dictionary) {
12            // Get the abbreviation of the word
13            String abbreviation = getAbbreviation(word);
14            // Add the word to the set corresponding to its abbreviation
15            // Create a new HashSet if the abbreviation doesn't exist yet
16            abbreviationMap.computeIfAbsent(abbreviation, key -> new HashSet<>()).add(word);
17        }
18    }
19
20    /**
21     * Check if a word's abbreviation is unique in the dictionary
22     * A word's abbreviation is unique if:
23     * 1. No word in the dictionary has the same abbreviation, OR
24     * 2. The only word with this abbreviation is the word itself
25     * @param word The word to check
26     * @return true if the abbreviation is unique, false otherwise
27     */
28    public boolean isUnique(String word) {
29        String abbreviation = getAbbreviation(word);
30        Set<String> wordsWithSameAbbreviation = abbreviationMap.get(abbreviation);
31      
32        // Return true if no words have this abbreviation, or
33        // if only one word has this abbreviation and it's the same as the input word
34        return wordsWithSameAbbreviation == null || 
35               (wordsWithSameAbbreviation.size() == 1 && wordsWithSameAbbreviation.contains(word));
36    }
37
38    /**
39     * Generate abbreviation for a word
40     * Rules: 
41     * - If word length < 3, return the word itself
42     * - Otherwise, return first letter + (length - 2) + last letter
43     * @param word The word to abbreviate
44     * @return The abbreviation of the word
45     */
46    private String getAbbreviation(String word) {
47        int length = word.length();
48        if (length < 3) {
49            return word;
50        }
51        // Concatenate first character, middle count, and last character
52        return word.substring(0, 1) + (length - 2) + word.substring(length - 1);
53    }
54}
55
56/**
57 * Your ValidWordAbbr object will be instantiated and called as such:
58 * ValidWordAbbr obj = new ValidWordAbbr(dictionary);
59 * boolean param_1 = obj.isUnique(word);
60 */
61
1class ValidWordAbbr {
2public:
3    /**
4     * Constructor: Initializes the data structure with the given dictionary.
5     * Groups words by their abbreviations.
6     * @param dictionary: Vector of words to preprocess
7     */
8    ValidWordAbbr(vector<string>& dictionary) {
9        for (auto& word : dictionary) {
10            // Store each word grouped by its abbreviation
11            abbreviationMap[getAbbreviation(word)].insert(word);
12        }
13    }
14  
15    /**
16     * Checks if a word's abbreviation is unique in the dictionary.
17     * A word's abbreviation is unique if:
18     * 1. No word in the dictionary has the same abbreviation, OR
19     * 2. The only word(s) with this abbreviation is the word itself
20     * @param word: The word to check
21     * @return: True if the abbreviation is unique, false otherwise
22     */
23    bool isUnique(string word) {
24        string abbreviation = getAbbreviation(word);
25      
26        // Check if abbreviation doesn't exist OR 
27        // exists with only one unique word which is the query word itself
28        return !abbreviationMap.count(abbreviation) || 
29               (abbreviationMap[abbreviation].size() == 1 && 
30                abbreviationMap[abbreviation].count(word));
31    }
32  
33private:
34    // Maps abbreviation to set of original words that have this abbreviation
35    unordered_map<string, unordered_set<string>> abbreviationMap;
36  
37    /**
38     * Generates abbreviation for a word.
39     * Rules: 
40     * - If word length < 3: return the word as is
41     * - Otherwise: first_letter + (length-2) + last_letter
42     * @param word: The word to abbreviate
43     * @return: The abbreviation string
44     */
45    string getAbbreviation(string& word) {
46        int length = word.size();
47      
48        if (length < 3) {
49            return word;
50        }
51      
52        // Format: first letter + middle count + last letter
53        return word.substr(0, 1) + to_string(length - 2) + word.substr(length - 1, 1);
54    }
55};
56
57/**
58 * Your ValidWordAbbr object will be instantiated and called as such:
59 * ValidWordAbbr* obj = new ValidWordAbbr(dictionary);
60 * bool param_1 = obj->isUnique(word);
61 */
62
1// Map to store abbreviations and their corresponding words
2// Key: abbreviation string, Value: Set of original words
3let abbreviationMap: Map<string, Set<string>> = new Map();
4
5/**
6 * Initialize the abbreviation map with a dictionary of words
7 * @param dictionary - Array of words to process
8 */
9function ValidWordAbbr(dictionary: string[]): void {
10    // Clear the map for fresh initialization
11    abbreviationMap = new Map();
12  
13    // Process each word in the dictionary
14    for (const word of dictionary) {
15        // Generate abbreviation for the current word
16        const abbreviation = abbr(word);
17      
18        // Initialize set if abbreviation doesn't exist
19        if (!abbreviationMap.has(abbreviation)) {
20            abbreviationMap.set(abbreviation, new Set());
21        }
22      
23        // Add the word to the set for this abbreviation
24        abbreviationMap.get(abbreviation)!.add(word);
25    }
26}
27
28/**
29 * Check if a word's abbreviation is unique
30 * A word's abbreviation is unique if:
31 * 1. No other word in the dictionary has the same abbreviation, OR
32 * 2. The only word(s) with the same abbreviation is the word itself
33 * @param word - The word to check
34 * @returns True if the abbreviation is unique, false otherwise
35 */
36function isUnique(word: string): boolean {
37    // Get the set of words with the same abbreviation
38    const wordsWithSameAbbr = abbreviationMap.get(abbr(word));
39  
40    // Check if abbreviation doesn't exist or only contains the word itself
41    return wordsWithSameAbbr === undefined || 
42           (wordsWithSameAbbr.size === 1 && wordsWithSameAbbr.has(word));
43}
44
45/**
46 * Generate abbreviation for a word
47 * Rules:
48 * - If word length < 3, return the word as is
49 * - Otherwise, return first letter + (length - 2) + last letter
50 * @param word - The word to abbreviate
51 * @returns The abbreviation string
52 */
53function abbr(word: string): string {
54    const length = word.length;
55  
56    // Words shorter than 3 characters remain unchanged
57    if (length < 3) {
58        return word;
59    }
60  
61    // Format: first letter + middle count + last letter
62    return word[0] + (length - 2) + word[length - 1];
63}
64

Time and Space Complexity

Time Complexity:

  • Initialization (__init__): O(n) where n is the total number of words in the dictionary. We iterate through each word once, and for each word, we compute its abbreviation in O(1) time (accessing first and last characters and calculating length) and add it to a set in O(1) average time.

  • isUnique method: O(m) where m is the number of words in the set self.d[s]. In the worst case, we need to check all words in the set to verify if they all equal the input word. However, in practice, m is typically small, making this close to O(1) for most cases.

  • abbr method: O(1) as it only accesses the first and last characters of the string and performs basic arithmetic operations.

Space Complexity:

  • O(n) where n is the total number of unique words in the dictionary. The hash table stores at most n words distributed across different abbreviation keys. Each abbreviation key maps to a set of words, and the total number of words stored across all sets is exactly n.

Common Pitfalls

1. Not Handling Duplicate Words in Dictionary

The Pitfall: Many implementations fail to account for duplicate words in the input dictionary. If the dictionary contains ["dog", "dog"], storing them in a list would incorrectly count "dog" twice when checking uniqueness, leading to wrong results.

Why It Matters: When checking isUnique("dog") with dictionary ["dog", "dog"], a naive list-based approach might conclude it's not unique because it finds multiple entries, even though they're all the same word.

The Solution: Use a set instead of a list to store words for each abbreviation. This automatically handles duplicates:

# Correct: Using set
self.abbreviation_map = defaultdict(set)
self.abbreviation_map[abbreviation].add(word)  # Duplicates automatically handled

# Incorrect: Using list
self.abbreviation_map = defaultdict(list)
self.abbreviation_map[abbreviation].append(word)  # Would store duplicates

2. Incorrect Abbreviation for Short Words

The Pitfall: Forgetting that words with length less than 3 should not be abbreviated. A common mistake is trying to create abbreviations like "a1" for "ab" or accessing out-of-bounds indices.

Why It Matters: The problem specifically states that 2-character words remain unchanged. Attempting to abbreviate "it" as "i0t" would be incorrect.

The Solution: Always check the word length before creating an abbreviation:

def _get_abbreviation(self, word: str) -> str:
    if len(word) < 3:  # Critical check
        return word
    return f"{word[0]}{len(word) - 2}{word[-1]}"

3. Misunderstanding the Uniqueness Condition

The Pitfall: A common misinterpretation is checking if the word exists in the dictionary rather than checking if its abbreviation conflicts with different words. Some incorrectly implement:

# WRONG: This checks if the word is in dictionary
return word not in self.all_words

Why It Matters: The uniqueness is about abbreviation conflicts, not word existence. If dictionary has ["deer"] and we check isUnique("door"), it should return false because both abbreviate to "d2r", even though "door" isn't in the dictionary.

The Solution: Check if all words with the same abbreviation are identical to the query word:

# Correct: Check if all words with same abbreviation are the same as input
return all(existing_word == word for existing_word in self.abbreviation_map[abbreviation])

4. Not Handling Empty Abbreviation Groups

The Pitfall: When an abbreviation doesn't exist in the map, accessing it without proper handling could cause errors or incorrect logic.

Why It Matters: If we check isUnique("make") and no word has abbreviation "m2e", we need to return true without errors.

The Solution: Either use defaultdict(set) or explicitly check for key existence:

# Solution 1: Using defaultdict (preferred)
self.abbreviation_map = defaultdict(set)

# Solution 2: Explicit checking
return (abbreviation not in self.abbreviation_map or 
        all(word == w for w in self.abbreviation_map[abbreviation]))
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Consider the classic dynamic programming of fibonacci numbers, what is the recurrence relation?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More