Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

527. Word Abbreviation - Leetcode Solution

Problem Description

The "Word Abbreviation" problem asks you to generate the minimal unique abbreviation for each word in a given list of words. An abbreviation for a word is constructed by keeping its first letter, then a number representing how many characters are omitted in the middle, and finally the last letter. For example, the word localization can be abbreviated as l10n (the 10 represents the 10 letters omitted between 'l' and 'n').

However, if two or more words share the same abbreviation, you must increase the prefix length (i.e., include more starting characters before the number) until every abbreviation in the list is unique. The abbreviation must be as short as possible while ensuring uniqueness within the list.

Constraints:

  • Each word consists of lowercase English letters.
  • All words are of length at least 2.
  • Abbreviations must be unique among the given words.
  • Do not abbreviate words of length less than or equal to 3 (they stay unchanged).

Thought Process

At first glance, the problem seems straightforward: abbreviate every word using the first letter, the count of omitted letters, and the last letter. However, conflicts arise when two or more words have the same abbreviation. The naive approach would be to generate abbreviations and check for duplicates, but this can become inefficient, especially for large word lists.

To optimize, we need a way to systematically resolve conflicts by gradually increasing the prefix length until all abbreviations are unique. This suggests a grouping strategy: for each set of words sharing an abbreviation, we increase the prefix for just those words, repeating as necessary.

Analogously, it's like giving people nicknames in a group: if two people have the same nickname, you add more of their first name until everyone is uniquely identified.

Solution Approach

Let's break down the solution step by step:

  1. Initial Abbreviation:
    • For each word, create its initial abbreviation using the first letter, the number of omitted letters, and the last letter.
    • If the word's length is less than or equal to 3, leave it as is.
  2. Conflict Detection:
    • Group words by their current abbreviation.
    • If a group has only one word, its abbreviation is unique.
    • If a group has more than one word, there's a conflict that needs to be resolved.
  3. Prefix Extension:
    • For each conflicting group, increase the prefix length by 1 for all words in that group and generate a new abbreviation.
    • Repeat the grouping and resolving process until all abbreviations are unique.
  4. Efficiency:
    • Use a hash map (dictionary) to group words by abbreviation efficiently (O(1) lookup).
    • Track the current prefix length for each word so we can increment only as needed.
  5. Return:
    • Once all abbreviations are unique, return the list of abbreviations in the order of the original words.

Example Walkthrough

Input: ["like", "god", "internal", "me", "internet", "interval", "intension", "face", "intrusion"]

  1. Initial Abbreviations:
    • "like" → "l2e"
    • "god" → "god" (length ≤ 3)
    • "internal" → "i6l"
    • "me" → "me" (length ≤ 3)
    • "internet" → "i6t"
    • "interval" → "i6l"
    • "intension" → "i7n"
    • "face" → "f2e"
    • "intrusion" → "i7n"
  2. Find Conflicts:
    • "i6l": ["internal", "interval"]
    • "i7n": ["intension", "intrusion"]
  3. Resolve "i6l":
    • Increase prefix to 2: "in4l" for both
    • Still conflict: both are "in4l"
    • Increase prefix to 3:
      • "internal" → "int3l"
      • "interval" → "int4l"
    • No conflict now.
  4. Resolve "i7n":
    • Increase prefix to 2: "in5n" for both
    • Still conflict: both are "in5n"
    • Increase prefix to 3:
      • "intension" → "int5n"
      • "intrusion" → "int5n"
    • Still conflict.
    • Increase prefix to 4:
      • "intension" → "inte4n"
      • "intrusion" → "intr5n"
    • No conflict now.
  5. Final abbreviations:
    • "like" → "l2e"
    • "god" → "god"
    • "internal" → "int3l"
    • "me" → "me"
    • "internet" → "i6t"
    • "interval" → "int4l"
    • "intension" → "inte4n"
    • "face" → "f2e"
    • "intrusion" → "intr5n"

Time and Space Complexity

Brute-force approach:

  • For each word, check all other words for abbreviation conflicts, and incrementally increase prefix length.
  • This results in O(N^2 * L) time complexity, where N is number of words and L is the max word length.
  • Space complexity is O(N*L) for storing abbreviations and words.
Optimized approach:
  • Using hash maps to group and resolve conflicts reduces redundant checks.
  • Each conflict group is resolved independently, and prefix increases only as needed, leading to an average time complexity of O(N*L).
  • Space complexity remains O(N*L) due to storage of abbreviations and prefix indices.

The optimized approach is much faster in practice, especially for large, diverse word lists.

Summary

To solve the Word Abbreviation problem, we iteratively generate the shortest possible abbreviation for each word, resolving conflicts by increasing the prefix length only where necessary. This strategy ensures all abbreviations are unique and as concise as possible. Using hash maps for grouping and conflict resolution provides efficient lookups and updates, making the solution both elegant and performant. The approach balances clarity and efficiency, and can be adapted to similar problems involving minimal unique representations.

Code Implementation

from collections import defaultdict

def abbreviate(word, prefix_len):
    if len(word) <= 3:
        return word
    if prefix_len >= len(word) - 2:
        return word
    num = len(word) - prefix_len - 1
    return word[:prefix_len] + str(num) + word[-1]

def wordsAbbreviation(words):
    n = len(words)
    prefix_lens = [1] * n
    result = [None] * n

    while True:
        abbr_dict = defaultdict(list)
        for i, word in enumerate(words):
            abbr = abbreviate(word, prefix_lens[i])
            result[i] = abbr
            abbr_dict[abbr].append(i)
        unique = True
        for abbr, idxs in abbr_dict.items():
            if len(idxs) > 1:
                unique = False
                for idx in idxs:
                    prefix_lens[idx] += 1
        if unique:
            break
    return result
    
#include <vector>
#include <string>
#include <unordered_map>

using namespace std;

string abbreviate(const string& word, int prefix_len) {
    if (word.size() <= 3) return word;
    if (prefix_len >= word.size() - 2) return word;
    int num = word.size() - prefix_len - 1;
    return word.substr(0, prefix_len) + to_string(num) + word.back();
}

vector<string> wordsAbbreviation(vector<string>& words) {
    int n = words.size();
    vector<int> prefix_lens(n, 1);
    vector<string> result(n);
    while (true) {
        unordered_map<string, vector<int>> abbr_dict;
        for (int i = 0; i < n; ++i) {
            string abbr = abbreviate(words[i], prefix_lens[i]);
            result[i] = abbr;
            abbr_dict[abbr].push_back(i);
        }
        bool unique = true;
        for (auto& p : abbr_dict) {
            if (p.second.size() > 1) {
                unique = false;
                for (int idx : p.second) {
                    prefix_lens[idx]++;
                }
            }
        }
        if (unique) break;
    }
    return result;
}
    
import java.util.*;

public class Solution {
    private String abbreviate(String word, int prefixLen) {
        if (word.length() <= 3) return word;
        if (prefixLen >= word.length() - 2) return word;
        int num = word.length() - prefixLen - 1;
        return word.substring(0, prefixLen) + num + word.charAt(word.length() - 1);
    }

    public List<String> wordsAbbreviation(List<String> words) {
        int n = words.size();
        int[] prefixLens = new int[n];
        Arrays.fill(prefixLens, 1);
        String[] result = new String[n];
        while (true) {
            Map<String, List<Integer>> abbrDict = new HashMap<>();
            for (int i = 0; i < n; i++) {
                String abbr = abbreviate(words.get(i), prefixLens[i]);
                result[i] = abbr;
                abbrDict.computeIfAbsent(abbr, k -> new ArrayList<>()).add(i);
            }
            boolean unique = true;
            for (List<Integer> idxs : abbrDict.values()) {
                if (idxs.size() > 1) {
                    unique = false;
                    for (int idx : idxs) {
                        prefixLens[idx]++;
                    }
                }
            }
            if (unique) break;
        }
        return Arrays.asList(result);
    }
}
    
function abbreviate(word, prefixLen) {
    if (word.length <= 3) return word;
    if (prefixLen >= word.length - 2) return word;
    let num = word.length - prefixLen - 1;
    return word.slice(0, prefixLen) + num + word[word.length - 1];
}

function wordsAbbreviation(words) {
    const n = words.length;
    const prefixLens = Array(n).fill(1);
    const result = Array(n);
    while (true) {
        const abbrDict = {};
        for (let i = 0; i < n; i++) {
            const abbr = abbreviate(words[i], prefixLens[i]);
            result[i] = abbr;
            if (!abbrDict[abbr]) abbrDict[abbr] = [];
            abbrDict[abbr].push(i);
        }
        let unique = true;
        for (const abbr in abbrDict) {
            if (abbrDict[abbr].length > 1) {
                unique = false;
                for (const idx of abbrDict[abbr]) {
                    prefixLens[idx]++;
                }
            }
        }
        if (unique) break;
    }
    return result;
}