Word Abbreviation solution and explanation
5 more videos on this problem
LeetCode 527. Word AbbreviationHappy Coding · 33:17 · 3K views
527. Word Abbreviation - Week 4/5 Leetcode July ChallengeProgramming Live with Larry · 13:08 · 473 views
Leetcode 527. Word AbbreviationAnnette Odingo · 55:56 · 133 views
Heavenislost Leetcode #527 Word AbbreviationLei Feng · 18:26 · 111 views
LeetCode Hard: 527. Word Abbreviationrobzingcod · 30:37 · 39 views
LeetCode 527: Word AbbreviationAlitaCode · 30:44 · 16 views
The problem
Given an array of distinct strings words, return the minimal possible abbreviations for every word.
The following are the rules for a string abbreviation:
- The initial abbreviation for each word is: the first character, then the number of characters in between, followed by the last character.
- If more than one word shares the same abbreviation, then perform the following operation:
- Increase the prefix (characters in the first part) of each of their abbreviations by
1.- For example, say you start with the words
["abcdef","abndef"]both initially abbreviated as"a4f". Then, a sequence of operations would be["a4f","a4f"]->["ab3f","ab3f"]->["abc2f","abn2f"].
- For example, say you start with the words
- This operation is repeated until every abbreviation is unique.
- Increase the prefix (characters in the first part) of each of their abbreviations by
- At the end, if an abbreviation did not make a word shorter, then keep it as the original word.
Example 1:
Input: words = ["like","god","internal","me","internet","interval","intension","face","intrusion"] Output: ["l2e","god","internal","me","i6t","interval","inte4n","f2e","intr4n"]
Example 2:
Input: words = ["aa","aaa"] Output: ["aa","aaa"]
Constraints:
1 <= words.length <= 4002 <= words[i].length <= 400words[i]consists of lowercase English letters.- All the strings of
wordsare unique.
Approach overview
Problem Overview: Given a list of distinct words, generate the shortest possible abbreviation for each word such that no two abbreviations collide. An abbreviation keeps the first letter, last letter, and replaces the middle characters with their count. If multiple words produce the same abbreviation, you must increase the prefix length until every abbreviation becomes unique.
Approach 1: Pairwise Prefix Expansion (Brute Force) (Time: O(n2 * L), Space: O(n))
Start by generating the default abbreviation for every word using prefix length 1. Then compare every pair of words. When two abbreviations collide, increase the prefix length for both words and recompute their abbreviations. This process continues until all conflicts disappear. The key operation is repeatedly checking pairs and rebuilding abbreviations, which becomes expensive for large inputs. This approach is straightforward and demonstrates the rule behind abbreviation conflicts but scales poorly because every conflict may trigger multiple recomputations.
Approach 2: Sorting + Longest Common Prefix (Time: O(n log n * L), Space: O(n))
Sort words by their potential abbreviation group: same first letter, last letter, and length. Within each group, only nearby words in sorted order can collide. Compute the longest common prefix between adjacent words to determine the minimum prefix required to differentiate them. This reduces unnecessary comparisons across unrelated words. Sorting clusters similar words together, and prefix comparison determines how many characters must remain uncompressed. This approach improves performance significantly and uses standard sorting and string operations.
Approach 3: Grouped Trie (Optimal) (Time: O(n * L), Space: O(n * L))
Group words by (length, first character, last character) because only these can produce identical abbreviations. For each group, build a Trie where each node tracks how many words pass through it. While inserting characters, the first node with count 1 identifies the unique prefix length for that word. Once the unique prefix is known, build the abbreviation using that prefix plus the compressed middle count. The Trie efficiently detects the earliest divergence between words, eliminating repeated comparisons. Each character is processed once during insertion and lookup, producing near-linear complexity relative to total characters.
Recommended for interviews: The grouped Trie approach is the expected solution. It demonstrates control over array grouping, Trie design, and greedy prefix selection. Mentioning the brute-force idea shows you understand the conflict rule, but implementing the Trie-based grouping proves you can scale the solution efficiently.
Solution
We notice that if two words have the same abbreviation, their first and last letters must be the same, and their lengths must be the same. Therefore, we can group all words by length and last letter, and use a trie to store the information of each group of words.
The structure of each node in the trie is as follows:
children: An array of length26, representing all child nodes of this node.cnt: The number of words passing through this node.
For each word, we insert it into the trie and record the cnt value of each node.
When querying, we start from the root node. For the current letter, if the cnt value of its corresponding child node is 1, then we have found the unique abbreviation, and we return the length of the current prefix. Otherwise, we continue to traverse downwards. After the traversal, if we have not found a unique abbreviation, then we return the length of the original word. After getting the prefix lengths of all words, we check whether the abbreviation of the word is shorter than the original word. If it is, then we add it to the answer, otherwise we add the original word to the answer.
The time complexity is O(L), and the space complexity is O(L). Here, L is the sum of the lengths of all words.
class Trie:
__slots__ = ["children", "cnt"]
def __init__(self):
self.children = [None] * 26
self.cnt = 0
def insert(self, w: str):
node = self
for c in w:
idx = ord(c) - ord("a")
if not node.children[idx]:
node.children[idx] = Trie()
node = node.children[idx]
node.cnt += 1
def search(self, w: str) -> int:
node = self
cnt = 0
for c in w:
cnt += 1
idx = ord(c) - ord("a")
node = node.children[idx]
if node.cnt == 1:
return cnt
return len(w)
class Solution:
def wordsAbbreviation(self, words: List[str]) -> List[str]:
tries = {}
for w in words:
m = len(w)
if (m, w[-1]) not in tries:
tries[(m, w[-1])] = Trie()
tries[(m, w[-1])].insert(w)
ans = []
for w in words:
cnt = tries[(len(w), w[-1])].search(w)
ans.append(
w if cnt + 2 >= len(w) else w[:cnt] + str(len(w) - cnt - 1) + w[-1]
)
return ansclass Trie {
private final Trie[] children = new Trie[26];
private int cnt;
public void insert(String w) {
Trie node = this;
for (char c : w.toCharArray()) {
int idx = c - 'a';
if (node.children[idx] == null) {
node.children[idx] = new Trie();
}
node = node.children[idx];
++node.cnt;
}
}
public int search(String w) {
Trie node = this;
int ans = 0;
for (char c : w.toCharArray()) {
++ans;
int idx = c - 'a';
node = node.children[idx];
if (node.cnt == 1) {
return ans;
}
}
return w.length();
}
}
class Solution {
public List<String> wordsAbbreviation(List<String> words) {
Map<List<Integer>, Trie> tries = new HashMap<>();
for (var w : words) {
var key = List.of(w.length(), w.charAt(w.length() - 1) - 'a');
tries.putIfAbsent(key, new Trie());
tries.get(key).insert(w);
}
List<String> ans = new ArrayList<>();
for (var w : words) {
int m = w.length();
var key = List.of(m, w.charAt(m - 1) - 'a');
int cnt = tries.get(key).search(w);
ans.add(cnt + 2 >= m ? w : w.substring(0, cnt) + (m - cnt - 1) + w.substring(m - 1));
}
return ans;
}
}class Trie {
public:
Trie()
: cnt(0) {
fill(children.begin(), children.end(), nullptr);
}
void insert(const string& w) {
Trie* node = this;
for (char c : w) {
int idx = c - 'a';
if (node->children[idx] == nullptr) {
node->children[idx] = new Trie();
}
node = node->children[idx];
++node->cnt;
}
}
int search(const string& w) {
Trie* node = this;
int ans = 0;
for (char c : w) {
++ans;
int idx = c - 'a';
node = node->children[idx];
if (node->cnt == 1) {
return ans;
}
}
return w.size();
}
private:
array<Trie*, 26> children;
int cnt;
};
class Solution {
public:
vector<string> wordsAbbreviation(vector<string>& words) {
map<pair<int, int>, Trie*> tries;
for (const auto& w : words) {
pair<int, int> key = {static_cast<int>(w.size()), w.back() - 'a'};
if (tries.find(key) == tries.end()) {
tries[key] = new Trie();
}
tries[key]->insert(w);
}
vector<string> ans;
for (const auto& w : words) {
int m = w.size();
pair<int, int> key = {m, w.back() - 'a'};
int cnt = tries[key]->search(w);
ans.push_back((cnt + 2 >= m) ? w : w.substr(0, cnt) + to_string(m - cnt - 1) + w.back());
}
return ans;
}
};class Trie {
private children: Trie[] = Array(26);
private cnt: number = 0;
insert(w: string): void {
let node: Trie = this;
for (const c of w) {
const idx: number = c.charCodeAt(0) - 'a'.charCodeAt(0);
if (!node.children[idx]) {
node.children[idx] = new Trie();
}
node = node.children[idx];
node.cnt++;
}
}
search(w: string): number {
let node: Trie = this;
let ans: number = 0;
for (const c of w) {
ans++;
const idx: number = c.charCodeAt(0) - 'a'.charCodeAt(0);
node = node.children[idx];
if (node.cnt === 1) {
return ans;
}
}
return w.length;
}
}
function wordsAbbreviation(words: string[]): string[] {
const tries: Map<string, Trie> = new Map();
for (const w of words) {
const key: string = `${w.length}-${w.charCodeAt(w.length - 1) - 'a'.charCodeAt(0)}`;
if (!tries.get(key)) {
tries.set(key, new Trie());
}
tries.get(key)!.insert(w);
}
const ans: string[] = [];
for (const w of words) {
const m: number = w.length;
const key: string = `${m}-${w.charCodeAt(m - 1) - 'a'.charCodeAt(0)}`;
const cnt: number = tries.get(key)!.search(w);
ans.push(cnt + 2 >= m ? w : w.substring(0, cnt) + (m - cnt - 1) + w.substring(m - 1));
}
return ans;
}type Trie struct {
children [26]*Trie
cnt int
}
func (t *Trie) insert(w string) {
node := t
for _, c := range w {
idx := c - 'a'
if node.children[idx] == nil {
node.children[idx] = &Trie{}
}
node = node.children[idx]
node.cnt++
}
}
func (t *Trie) search(w string) int {
node := t
ans := 0
for _, c := range w {
ans++
idx := c - 'a'
node = node.children[idx]
if node.cnt == 1 {
return ans
}
}
return len(w)
}
func wordsAbbreviation(words []string) (ans []string) {
tries := make(map[[2]int]*Trie)
for _, w := range words {
key := [2]int{len(w), int(w[len(w)-1] - 'a')}
_, exists := tries[key]
if !exists {
tries[key] = &Trie{}
}
tries[key].insert(w)
}
for _, w := range words {
m := len(w)
key := [2]int{m, int(w[m-1] - 'a')}
cnt := tries[key].search(w)
if cnt+2 >= m {
ans = append(ans, w)
} else {
abbr := w[:cnt] + fmt.Sprintf("%d", m-cnt-1) + w[m-1:]
ans = append(ans, abbr)
}
}
return
}Complexity analysis
| Approach | Time | Space | When to Use |
|---|---|---|---|
| Pairwise Prefix Expansion (Brute Force) | O(n^2 * L) | O(n) | Small input sizes or when demonstrating the basic conflict resolution logic |
| Sorting + Longest Common Prefix | O(n log n * L) | O(n) | When using standard string operations without building additional data structures |
| Grouped Trie (Optimal) | O(n * L) | O(n * L) | Best for large datasets and interview settings where efficient prefix detection is required |
Frequently asked questions
Is Word Abbreviation easy or hard?
Word Abbreviation is considered a hard problem because it combines greedy abbreviation rules with prefix uniqueness detection. Efficient solutions require grouping logic and a Trie to avoid quadratic comparisons.
Word Abbreviation Python/Java solution
Implement the grouped Trie approach. First group words by length and boundary letters, then build a Trie for each group while counting prefix frequencies. After determining the unique prefix length for each word, generate the abbreviation using prefix + skipped length + last character.
How to solve Word Abbreviation in O(n)?
Group words with the same length, first letter, and last letter. Build a Trie for each group and track how many words pass through each node. The first node with count 1 gives the shortest unique prefix, allowing the abbreviation to be generated in linear time relative to total characters.
What is the best approach for Word Abbreviation?
The grouped Trie approach is the most efficient and commonly expected interview solution. Words are grouped by length and boundary characters, then inserted into a Trie to find the earliest unique prefix. This avoids repeated comparisons and runs in O(n * L) time where L is the average word length.
Is Word Abbreviation asked at Google/Amazon/Meta?
Word Abbreviation is a classic string and Trie design problem that has appeared in interviews at large tech companies including Google and Meta. It tests conflict resolution, prefix uniqueness, and efficient string processing.
What data structure is used in Word Abbreviation?
The optimal solution uses a Trie (prefix tree). The Trie helps track how many words share the same prefix, making it easy to determine the shortest unique prefix required to avoid abbreviation collisions.
What is the time complexity of Word Abbreviation?
The optimal grouped Trie solution runs in O(n * L) time because each character of every word is inserted and traversed once. Space complexity is also O(n * L) due to the Trie structure storing characters for all grouped words.
