Facebook Pixel

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:

  1. 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").

  2. Non-empty substrings: You cannot replace an empty substring. Numbers must be at least 1.

  3. No leading zeros: Numbers cannot have leading zeros. For example, "s010n" is invalid.

  4. 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.

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 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:

  1. Match 's' at position 0
  2. Skip 10 characters (the number 10 tells us this)
  3. 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 and abbr[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 the word
  • Pointer j for traversing the abbr
  • Variable x to accumulate multi-digit numbers

Step-by-step implementation:

  1. Initialize pointers and accumulator: Start with i = j = x = 0. These track our positions in both strings and any number being built.

  2. Main loop: While both i < m and j < n (where m and n are lengths of word and abbr):

    Case 1: Current character in abbr is a digit

    • Check for leading zeros: If abbr[j] == '0' and x == 0, this is an invalid leading zero, return false
    • Build the number: x = x * 10 + int(abbr[j]). This handles multi-digit numbers like converting '1' followed by '0' into 10

    Case 2: Current character in abbr is a letter

    • First, apply any accumulated skip: Move i forward by x positions (i += x)
    • Reset the accumulator: x = 0
    • Validate the match:
      • If i >= m, we've gone beyond the word's length, return false
      • If word[i] != abbr[j], characters don't match, return false
    • If valid, move to next character in word: i += 1

    After processing either case, advance in abbreviation: j += 1

  3. 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 length
    • j == 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 by x=0, check word[0]='s' matches, move i=1, j=1
  • abbr[1]='1' (digit): Build x=1, move j=2
  • abbr[2]='0' (digit): Build x=10, move j=3
  • abbr[3]='n' (letter): Skip by x=10 making i=11, check word[11]='n' matches, move i=12, j=4
  • End: i+x=12+0=12 equals m=12, and j=4 equals n=4, return true

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 Evaluator

Example 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' vs abbr[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' vs abbr[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' vs abbr[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' vs abbr[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 in abbr exactly once, contributing O(n) operations
  • Pointer i advances through word either character by character or by jumping forward when processing digits in the abbreviation
  • In the worst case, i traverses through all characters in word, contributing O(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, and x for tracking positions and accumulating digit values
  • Two integer variables: m and n 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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which data structure is used in a depth first search?


Recommended Readings

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

Load More