Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

320. Generalized Abbreviation - Leetcode Solution

Problem Description

The Generalized Abbreviation problem asks you to generate all possible generalized abbreviations for a given word. An abbreviation replaces any number of consecutive characters with their count, but you cannot abbreviate non-consecutive letters in a single group. For example, given the word word, valid abbreviations include word, w1rd, 2rd, 1o1d, 4, etc.

  • You must generate all possible abbreviations for the input string word.
  • Each character can be either kept as-is or replaced by a count (number) that represents the number of consecutive abbreviated letters.
  • No two counts can be adjacent (e.g., 12d is not valid for word).
  • Return the list of all possible abbreviations as strings.

Thought Process

At first glance, this problem looks like a variation of generating all subsets or permutations, but with the twist that we can abbreviate any run of consecutive characters into a single number. The brute-force way would be to try all possible ways to abbreviate or not abbreviate each character, but we need to ensure that numbers are not repeated or adjacent.

For each character, we have two choices:

  • Abbreviate it: This increases the current abbreviation count.
  • Keep it: If we have a running abbreviation count, append it before the current character, then add the character itself, and reset the count.
This is a classic recursive backtracking problem, where each position in the string branches into two possibilities.

As we explore, we build up the abbreviation string. When we reach the end, if there's a non-zero abbreviation count, we append it to the result. This ensures no two numbers are adjacent and every abbreviation is valid.

Solution Approach

Let's break down the solution step-by-step:

  1. Use Backtracking (DFS):
    • We recursively explore each character position with two options: abbreviate or keep.
    • We pass along the current position, the abbreviation string built so far, and the current abbreviation count.
  2. When Abbreviating:
    • We don't append the character, but increase the abbreviation count by 1.
  3. When Keeping the Character:
    • If there's an abbreviation count, append it to the result string first (to ensure numbers are not adjacent).
    • Then append the current character, and reset the abbreviation count to zero.
  4. Base Case:
    • When the current position reaches the end of the word, if there's a running abbreviation count, append it.
    • Add the built abbreviation to the results list.
  5. Return the Results:
    • After exploring all branches, return the list of all generated abbreviations.

This approach ensures we generate every valid abbreviation exactly once, and by using recursion, the code remains clean and easy to follow.

Example Walkthrough

Let's walk through an example with the word word:

  • Start at position 0, with an empty string and count 0.
  • At each step, branch into two choices: abbreviate or keep the character.
  • Suppose we abbreviate all: w o r d4 (all four characters abbreviated).
  • Suppose we keep all: w o r dword.
  • Suppose we abbreviate first two, keep the rest: w o r d2rd.
  • Suppose we abbreviate first, keep second, abbreviate last two: w o r d1o2.
  • Suppose we keep first, abbreviate next two, keep last: w o r dw2d.
  • ...and so on for all combinations.

At each step, if we abbreviate, we increase the running count; if we keep, we append the count (if any) and the character, then reset.

Code Implementation

class Solution:
    def generateAbbreviations(self, word: str):
        res = []
        def backtrack(pos, cur, count):
            if pos == len(word):
                if count > 0:
                    cur += str(count)
                res.append(cur)
                return
            # Option 1: Abbreviate this character
            backtrack(pos + 1, cur, count + 1)
            # Option 2: Keep this character
            new_cur = cur
            if count > 0:
                new_cur += str(count)
            new_cur += word[pos]
            backtrack(pos + 1, new_cur, 0)
        backtrack(0, "", 0)
        return res
      
class Solution {
public:
    vector<string> generateAbbreviations(string word) {
        vector<string> res;
        backtrack(word, 0, "", 0, res);
        return res;
    }
    void backtrack(const string& word, int pos, string cur, int count, vector<string>& res) {
        if (pos == word.size()) {
            if (count > 0) cur += to_string(count);
            res.push_back(cur);
            return;
        }
        // Option 1: Abbreviate this character
        backtrack(word, pos + 1, cur, count + 1, res);
        // Option 2: Keep this character
        string new_cur = cur;
        if (count > 0) new_cur += to_string(count);
        new_cur += word[pos];
        backtrack(word, pos + 1, new_cur, 0, res);
    }
};
      
class Solution {
    public List<String> generateAbbreviations(String word) {
        List<String> res = new ArrayList<>();
        backtrack(word, 0, "", 0, res);
        return res;
    }
    private void backtrack(String word, int pos, String cur, int count, List<String> res) {
        if (pos == word.length()) {
            if (count > 0) cur += count;
            res.add(cur);
            return;
        }
        // Option 1: Abbreviate this character
        backtrack(word, pos + 1, cur, count + 1, res);
        // Option 2: Keep this character
        String newCur = cur;
        if (count > 0) newCur += count;
        newCur += word.charAt(pos);
        backtrack(word, pos + 1, newCur, 0, res);
    }
}
      
var generateAbbreviations = function(word) {
    const res = [];
    function backtrack(pos, cur, count) {
        if (pos === word.length) {
            if (count > 0) cur += count;
            res.push(cur);
            return;
        }
        // Option 1: Abbreviate this character
        backtrack(pos + 1, cur, count + 1);
        // Option 2: Keep this character
        let newCur = cur;
        if (count > 0) newCur += count;
        newCur += word[pos];
        backtrack(pos + 1, newCur, 0);
    }
    backtrack(0, "", 0);
    return res;
};
      

Time and Space Complexity

  • Brute-force: For a string of length n, there are 2^n ways to choose which characters to abbreviate or not. Each choice can produce a unique abbreviation. So, the number of abbreviations is O(2^n).
  • Optimized Backtracking (this approach): We still explore all 2^n combinations, but we avoid invalid forms (like adjacent numbers) by design. Thus, the time complexity is O(2^n) for generating all abbreviations, and space complexity is also O(2^n * n) as we store all output strings (some of which can be up to length n).
  • Why? Each character has two options (abbreviate or not), and we need to store every result. There is no way to avoid this exponential growth since all combinations are required.

Summary

The Generalized Abbreviation problem is a classic example of using backtracking to explore all possible ways to abbreviate a string by replacing consecutive characters with their counts. By making a decision at each character (to abbreviate or not), and carefully handling the abbreviation count, we efficiently generate all valid abbreviations without duplicates or invalid forms. The recursive approach is both elegant and intuitive, and it ensures that all possible abbreviations are covered with clean, readable code.