408. Valid Word Abbreviation π
Problem Description
Given a string word
and an abbreviation abbr
, determine if the abbreviation is valid for the given word.
An abbreviation is formed by replacing any number of non-adjacent, non-empty substrings with their lengths. The key rules are:
-
Non-adjacent substrings: You cannot replace two consecutive substrings with numbers. For example,
"s55n"
is invalid for"substitution"
because it tries to replace two adjacent substrings ("ubsti"
and"tutio"
). -
Non-empty substrings: You cannot replace an empty substring. Numbers must be at least 1.
-
No leading zeros: Numbers cannot have leading zeros. For example,
"s010n"
is invalid. -
Mixed format: An abbreviation can contain both letters from the original word and numbers representing the length of replaced substrings.
Examples of valid abbreviations for "substitution"
:
"s10n"
- keeps 's' and 'n', replaces the middle 10 characters"sub4u4"
- keeps "sub", replaces 4 characters, keeps "u", replaces 4 characters"12"
- replaces all 12 characters"substitution"
- no replacements, the full word itself
Examples of invalid abbreviations:
"s55n"
- adjacent replacements (5 and 5 are next to each other)"s010n"
- leading zero in the number 010"s0ubstitution"
- tries to replace 0 characters (empty substring)
The task is to verify if a given abbreviation correctly matches the original word by checking if the letters match at their positions and the numbers correctly represent the count of skipped characters.
Intuition
The key insight is that we need to traverse both strings simultaneously and verify that they match according to the abbreviation rules. Think of it like following a set of instructions where letters tell us to match exactly, and numbers tell us to skip ahead.
When we encounter a letter in abbr
, it must match the corresponding position in word
. When we encounter a number, it tells us how many characters to skip in word
. The challenge is handling multi-digit numbers correctly.
Consider the example: word = "substitution"
and abbr = "s10n"
. We need to:
- Match 's' at position 0
- Skip 10 characters (the number 10 tells us this)
- Match 'n' at position 11
This suggests using two pointers - one for each string. The pointer in word
advances either by 1 (when matching a letter) or by the number value (when we see digits in abbr
). The pointer in abbr
always advances by 1.
For multi-digit numbers, we need to accumulate the value. When we see '1'
followed by '0'
, we should interpret this as 10
, not as separate values. So we build the number: x = x * 10 + digit
.
The tricky part is validating the rules:
- Leading zeros: Check if the first digit is
'0'
when starting a new number (x == 0
andabbr[j] == '0'
) - Non-adjacent replacements: This is automatically handled since after processing a number, we must encounter a letter before the next number
- Bounds checking: Ensure we don't skip beyond the word's length
At the end, both strings should be fully consumed. The word pointer plus any remaining skip count should equal the word's length, and the abbreviation pointer should reach the end.
Solution Approach
We use a two-pointer simulation approach to validate the abbreviation. The algorithm maintains:
- Pointer
i
for traversing theword
- Pointer
j
for traversing theabbr
- Variable
x
to accumulate multi-digit numbers
Step-by-step implementation:
-
Initialize pointers and accumulator: Start with
i = j = x = 0
. These track our positions in both strings and any number being built. -
Main loop: While both
i < m
andj < n
(wherem
andn
are lengths ofword
andabbr
):Case 1: Current character in
abbr
is a digit- Check for leading zeros: If
abbr[j] == '0'
andx == 0
, this is an invalid leading zero, returnfalse
- Build the number:
x = x * 10 + int(abbr[j])
. This handles multi-digit numbers like converting'1'
followed by'0'
into10
Case 2: Current character in
abbr
is a letter- First, apply any accumulated skip: Move
i
forward byx
positions (i += x
) - Reset the accumulator:
x = 0
- Validate the match:
- If
i >= m
, we've gone beyond the word's length, returnfalse
- If
word[i] != abbr[j]
, characters don't match, returnfalse
- If
- If valid, move to next character in word:
i += 1
After processing either case, advance in abbreviation:
j += 1
- Check for leading zeros: If
-
Final validation: After the loop ends, check if both strings are fully consumed:
i + x == m
: The word pointer plus any remaining skip should equal word lengthj == n
: The abbreviation should be fully processed- Return
true
only if both conditions are met
Example walkthrough with word = "substitution"
, abbr = "s10n"
:
- Start:
i=0, j=0, x=0
abbr[0]='s'
(letter): Skip byx=0
, checkword[0]='s'
matches, movei=1, j=1
abbr[1]='1'
(digit): Buildx=1
, movej=2
abbr[2]='0'
(digit): Buildx=10
, movej=3
abbr[3]='n'
(letter): Skip byx=10
makingi=11
, checkword[11]='n'
matches, movei=12, j=4
- End:
i+x=12+0=12
equalsm=12
, andj=4
equalsn=4
, returntrue
This approach efficiently handles all validation rules in a single pass with O(n)
time complexity and O(1)
space complexity.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a detailed example with word = "internationalization"
and abbr = "i12iz4n"
.
Initial Setup:
word = "internationalization"
(length 20)abbr = "i12iz4n"
(length 7)i = 0, j = 0, x = 0
Step 1: j=0, abbr[0]='i'
(letter)
- Apply skip:
i = 0 + 0 = 0
- Reset:
x = 0
- Check match:
word[0]='i'
vsabbr[0]='i'
β - Advance:
i = 1, j = 1
Step 2: j=1, abbr[1]='1'
(digit)
- Not leading zero (x=0 but digit='1')
- Build number:
x = 0 * 10 + 1 = 1
- Advance:
j = 2
Step 3: j=2, abbr[2]='2'
(digit)
- Continue building:
x = 1 * 10 + 2 = 12
- Advance:
j = 3
Step 4: j=3, abbr[3]='i'
(letter)
- Apply skip:
i = 1 + 12 = 13
- Reset:
x = 0
- Check match:
word[13]='i'
vsabbr[3]='i'
β - Advance:
i = 14, j = 4
Step 5: j=4, abbr[4]='z'
(letter)
- Apply skip:
i = 14 + 0 = 14
- Reset:
x = 0
- Check match:
word[14]='z'
vsabbr[4]='z'
β - Advance:
i = 15, j = 5
Step 6: j=5, abbr[5]='4'
(digit)
- Build number:
x = 0 * 10 + 4 = 4
- Advance:
j = 6
Step 7: j=6, abbr[6]='n'
(letter)
- Apply skip:
i = 15 + 4 = 19
- Reset:
x = 0
- Check match:
word[19]='n'
vsabbr[6]='n'
β - Advance:
i = 20, j = 7
Final Check:
i + x = 20 + 0 = 20
equals word length βj = 7
equals abbr length β- Return
true
The abbreviation is valid! It represents: keep 'i', skip 12 characters ("nternational"), keep 'iz', skip 4 characters ("atio"), keep 'n'.
Solution Implementation
1class Solution:
2 def validWordAbbreviation(self, word: str, abbr: str) -> bool:
3 """
4 Check if abbreviation is a valid abbreviation of word.
5 Numbers in abbreviation represent the count of characters to skip.
6 Leading zeros are not allowed in numbers.
7
8 Args:
9 word: The original word to validate against
10 abbr: The abbreviation to validate
11
12 Returns:
13 True if abbr is a valid abbreviation of word, False otherwise
14 """
15 word_length = len(word)
16 abbr_length = len(abbr)
17
18 # Pointers for word and abbreviation
19 word_index = 0
20 abbr_index = 0
21
22 # Accumulator for multi-digit numbers in abbreviation
23 skip_count = 0
24
25 # Process both strings simultaneously
26 while word_index < word_length and abbr_index < abbr_length:
27 current_abbr_char = abbr[abbr_index]
28
29 if current_abbr_char.isdigit():
30 # Check for invalid leading zero
31 if current_abbr_char == '0' and skip_count == 0:
32 return False
33
34 # Build multi-digit number
35 skip_count = skip_count * 10 + int(current_abbr_char)
36 else:
37 # Apply accumulated skip count
38 word_index += skip_count
39 skip_count = 0
40
41 # Check if we've exceeded word bounds or characters don't match
42 if word_index >= word_length or word[word_index] != current_abbr_char:
43 return False
44
45 # Move to next character in word
46 word_index += 1
47
48 # Move to next character in abbreviation
49 abbr_index += 1
50
51 # Validate that both strings are fully processed
52 # Account for any remaining skip count
53 return word_index + skip_count == word_length and abbr_index == abbr_length
54
1class Solution {
2 public boolean validWordAbbreviation(String word, String abbr) {
3 int wordLength = word.length();
4 int abbrLength = abbr.length();
5
6 // Pointers for traversing word and abbreviation
7 int wordIndex = 0;
8 int abbrIndex = 0;
9
10 // Accumulator for building multi-digit numbers
11 int numberAccumulator = 0;
12
13 // Iterate through the abbreviation string
14 while (wordIndex < wordLength && abbrIndex < abbrLength) {
15 char currentChar = abbr.charAt(abbrIndex);
16
17 if (Character.isDigit(currentChar)) {
18 // Handle digit characters
19
20 // Check for leading zeros (invalid abbreviation)
21 if (currentChar == '0' && numberAccumulator == 0) {
22 return false;
23 }
24
25 // Build the number by accumulating digits
26 numberAccumulator = numberAccumulator * 10 + (currentChar - '0');
27 } else {
28 // Handle letter characters
29
30 // First, skip characters in word based on accumulated number
31 wordIndex += numberAccumulator;
32 numberAccumulator = 0; // Reset accumulator
33
34 // Check if we've gone out of bounds or characters don't match
35 if (wordIndex >= wordLength || word.charAt(wordIndex) != currentChar) {
36 return false;
37 }
38
39 // Move to next character in word
40 wordIndex++;
41 }
42
43 // Move to next character in abbreviation
44 abbrIndex++;
45 }
46
47 // Check if both strings are fully consumed
48 // Add any remaining accumulated number to wordIndex
49 return (wordIndex + numberAccumulator == wordLength) && (abbrIndex == abbrLength);
50 }
51}
52
1class Solution {
2public:
3 bool validWordAbbreviation(string word, string abbr) {
4 int wordLength = word.size();
5 int abbrLength = abbr.size();
6 int wordIndex = 0; // Current position in the word
7 int abbrIndex = 0; // Current position in the abbreviation
8 int skipCount = 0; // Accumulated number to skip
9
10 // Process each character in the abbreviation
11 for (; wordIndex < wordLength && abbrIndex < abbrLength; ++abbrIndex) {
12 if (isdigit(abbr[abbrIndex])) {
13 // Handle digit: build the number to skip
14
15 // Leading zeros are not allowed in valid abbreviations
16 if (abbr[abbrIndex] == '0' && skipCount == 0) {
17 return false;
18 }
19
20 // Accumulate the digit to form the complete number
21 skipCount = skipCount * 10 + (abbr[abbrIndex] - '0');
22 } else {
23 // Handle letter: first apply any pending skip, then match the letter
24
25 // Skip the specified number of characters in the word
26 wordIndex += skipCount;
27 skipCount = 0; // Reset skip count after using it
28
29 // Check if we've gone out of bounds or characters don't match
30 if (wordIndex >= wordLength || word[wordIndex] != abbr[abbrIndex]) {
31 return false;
32 }
33
34 // Move to next character in word after successful match
35 ++wordIndex;
36 }
37 }
38
39 // Valid if we've consumed entire abbreviation and matched entire word
40 // (including any remaining skip count)
41 return wordIndex + skipCount == wordLength && abbrIndex == abbrLength;
42 }
43};
44
1/**
2 * Validates if an abbreviation matches a given word.
3 * Numbers in the abbreviation represent the count of characters to skip.
4 *
5 * @param word - The original word to validate against
6 * @param abbr - The abbreviation to check
7 * @returns true if the abbreviation is valid for the word, false otherwise
8 */
9function validWordAbbreviation(word: string, abbr: string): boolean {
10 const wordLength: number = word.length;
11 const abbrLength: number = abbr.length;
12
13 let wordIndex: number = 0; // Current position in the word
14 let abbrIndex: number = 0; // Current position in the abbreviation
15 let skipCount: number = 0; // Accumulated number representing characters to skip
16
17 // Process each character in the abbreviation
18 while (wordIndex < wordLength && abbrIndex < abbrLength) {
19 const currentChar: string = abbr[abbrIndex];
20
21 // Check if current character is a digit
22 if (currentChar >= '0' && currentChar <= '9') {
23 // Leading zeros are not allowed in abbreviations
24 if (currentChar === '0' && skipCount === 0) {
25 return false;
26 }
27
28 // Accumulate the skip count (handle multi-digit numbers)
29 skipCount = skipCount * 10 + Number(currentChar);
30 abbrIndex++;
31 } else {
32 // Apply any accumulated skip count
33 wordIndex += skipCount;
34 skipCount = 0;
35
36 // Check if we've exceeded word bounds or characters don't match
37 if (wordIndex >= wordLength || word[wordIndex] !== currentChar) {
38 return false;
39 }
40
41 wordIndex++;
42 abbrIndex++;
43 }
44 }
45
46 // Apply any remaining skip count
47 wordIndex += skipCount;
48
49 // Both indices should reach the end of their respective strings
50 return wordIndex === wordLength && abbrIndex === abbrLength;
51}
52
Time and Space Complexity
Time Complexity: O(m + n)
, where m
is the length of the string word
and n
is the length of the string abbr
.
The algorithm uses two pointers i
and j
to traverse through word
and abbr
respectively. In the while loop:
- Pointer
j
traverses through each character inabbr
exactly once, contributingO(n)
operations - Pointer
i
advances throughword
either character by character or by jumping forward when processing digits in the abbreviation - In the worst case,
i
traverses through all characters inword
, contributingO(m)
operations - Each iteration performs constant time operations (digit checking, character comparison, arithmetic operations)
Since both strings are traversed at most once, the total time complexity is O(m + n)
.
Space Complexity: O(1)
The algorithm only uses a fixed number of variables:
- Three integer variables:
i
,j
, andx
for tracking positions and accumulating digit values - Two integer variables:
m
andn
for storing string lengths
No additional data structures are created that scale with the input size. All operations are performed in-place using constant extra space, resulting in O(1)
space complexity.
Common Pitfalls
1. Forgetting to Apply Remaining Skip Count in Final Validation
The Pitfall:
A common mistake is checking only word_index == word_length
at the end, forgetting that there might be a remaining skip count that hasn't been applied yet. For example, with word = "hi"
and abbr = "h1"
, after processing 'h', we have word_index = 1
and skip_count = 1
. If we only check word_index == word_length
, we'd incorrectly return False
.
Incorrect Final Check:
# Wrong - doesn't account for remaining skip_count return word_index == word_length and abbr_index == abbr_length
Correct Solution:
# Correct - adds remaining skip_count to word_index return word_index + skip_count == word_length and abbr_index == abbr_length
2. Mishandling Leading Zeros Detection
The Pitfall:
Simply checking if abbr[abbr_index] == '0'
is insufficient. We need to ensure it's a leading zero (start of a number), not a valid zero in a multi-digit number like "10".
Incorrect Check:
if current_abbr_char == '0': # Wrong - rejects valid numbers like "10" return False
Correct Solution:
if current_abbr_char == '0' and skip_count == 0: # Only reject if it's a leading zero return False
3. Processing Letters Before Applying Skip Count
The Pitfall: When encountering a letter in the abbreviation, forgetting to first apply any accumulated skip count before comparing characters leads to incorrect position matching.
Incorrect Order:
else: # Letter case # Wrong - comparing before skipping if word[word_index] != current_abbr_char: return False word_index += skip_count # Applied too late
Correct Solution:
else: # Letter case # First apply the skip word_index += skip_count skip_count = 0 # Then validate bounds and character match if word_index >= word_length or word[word_index] != current_abbr_char: return False
4. Not Resetting Skip Count After Use
The Pitfall: After applying a skip count when encountering a letter, failing to reset it to 0 causes the same skip to be incorrectly applied multiple times.
Missing Reset:
else: word_index += skip_count # Forgot: skip_count = 0 if word_index >= word_length or word[word_index] != current_abbr_char: return False
Correct Solution:
Always reset skip_count = 0
immediately after using it to prevent reuse.
Which data structure is used in a depth first search?
Recommended Readings
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Donβt Miss This!