Facebook Pixel

2645. Minimum Additions to Make Valid String

Problem Description

You are given a string word that consists only of the letters 'a', 'b', and 'c'. Your task is to insert additional letters ('a', 'b', or 'c') into this string to make it "valid".

A string is considered valid if it can be formed by concatenating the pattern "abc" multiple times. For example:

  • "abc" is valid (one repetition of "abc")
  • "abcabc" is valid (two repetitions of "abc")
  • "abcabcabc" is valid (three repetitions of "abc")

You can insert the letters 'a', 'b', or 'c' at any position in the string and any number of times. The goal is to find the minimum number of insertions needed to transform the given word into a valid string.

For example:

  • If word = "b", you need to insert 'a' before it and 'c' after it to get "abc", so the answer is 2
  • If word = "aaa", you need to insert 'b' and 'c' after the first 'a', then 'b' and 'c' after the second 'a', and 'b' and 'c' after the third 'a' to get "abcabcabc", so the answer is 6

The function should return the minimum number of letters that must be inserted.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

Think of the valid string as a repeating pattern of "abc". When we scan through the given word, we want to match each character with the expected character in the "abc" pattern.

The key insight is that we can traverse the word from left to right and try to match each character with the current expected character in the "abc" cycle. If the current character in word matches what we expect in the pattern, we can use it directly. If it doesn't match, we need to insert the missing character.

For example, if we're expecting an 'a' (at position 0 in "abc") but we see a 'b' in word, this means we're missing an 'a'. We count this as one insertion needed, then move to expecting the next character in the pattern (which would be 'b'), and now we can match it with the 'b' we see.

We use a cyclic pointer that moves through positions 0, 1, 2 (corresponding to 'a', 'b', 'c') repeatedly. This pointer i tells us which character we're currently expecting in the "abc" pattern. When we encounter a character in word:

  • If it matches s[i] where s = "abc", we move to the next character in word
  • If it doesn't match, we add 1 to our insertion count (representing the missing character)
  • In both cases, we advance i to the next position in the cycle using (i + 1) % 3

After processing all characters, we need to handle the ending. If the last character of word isn't 'c', we're in the middle of an "abc" pattern and need to complete it. If it ends with 'b', we need to add one 'c'. If it ends with 'a', we need to add both 'b' and 'c'.

This greedy approach works because we're always trying to use the existing characters in word optimally by matching them with the nearest valid position in the "abc" pattern.

Learn more about Stack, Greedy and Dynamic Programming patterns.

Solution Approach

We implement a greedy solution using two pointers to match characters from word with the expected "abc" pattern.

Setup:

  • Define s = "abc" as our pattern string
  • Initialize ans = 0 to count the number of insertions needed
  • Set pointer i = 0 to track our position in the "abc" pattern (0 for 'a', 1 for 'b', 2 for 'c')
  • Set pointer j = 0 to traverse through word

Main Loop: While j < n (where n is the length of word):

  1. Check if word[j] matches s[i]:

    • If word[j] != s[i]: We need to insert the missing character s[i], so increment ans += 1. Keep j at the same position since we haven't used this character yet.
    • If word[j] == s[i]: The character matches our expectation, so we can use it. Move j forward by 1 to process the next character in word.
  2. Move to the next expected character in the pattern: i = (i + 1) % 3. This cycles through 0→1→2→0 repeatedly, corresponding to 'a'→'b'→'c'→'a'.

Handle the Ending: After processing all characters, check if we need to complete the last "abc" pattern:

  • If word[-1] == 'c': The string ends properly at the end of a pattern, no additional insertions needed
  • If word[-1] == 'b': We need to add 'c' to complete the pattern, so add 1 to ans
  • If word[-1] == 'a': We need to add 'b' and 'c' to complete the pattern, so add 2 to ans

This is implemented as:

if word[-1] != 'c':
    ans += 1 if word[-1] == 'b' else 2

Time Complexity: O(n) where n is the length of word, as we traverse the string once.

Space Complexity: O(1) as we only use a constant amount of extra space for pointers and counters.

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 the solution with word = "bac".

Initial Setup:

  • s = "abc" (our pattern)
  • ans = 0 (insertion count)
  • i = 0 (expecting 'a' from pattern)
  • j = 0 (at 'b' in word)

Step 1:

  • Current character: word[0] = 'b'
  • Expected: s[0] = 'a'
  • Since 'b' ≠ 'a', we need to insert 'a'
  • ans = 1, j stays at 0
  • Move pattern pointer: i = (0+1)%3 = 1

Step 2:

  • Current character: word[0] = 'b'
  • Expected: s[1] = 'b'
  • Since 'b' = 'b', it matches!
  • j = 1 (move to next character)
  • Move pattern pointer: i = (1+1)%3 = 2

Step 3:

  • Current character: word[1] = 'a'
  • Expected: s[2] = 'c'
  • Since 'a' ≠ 'c', we need to insert 'c'
  • ans = 2, j stays at 1
  • Move pattern pointer: i = (2+1)%3 = 0

Step 4:

  • Current character: word[1] = 'a'
  • Expected: s[0] = 'a'
  • Since 'a' = 'a', it matches!
  • j = 2 (move to next character)
  • Move pattern pointer: i = (0+1)%3 = 1

Step 5:

  • Current character: word[2] = 'c'
  • Expected: s[1] = 'b'
  • Since 'c' ≠ 'b', we need to insert 'b'
  • ans = 3, j stays at 2
  • Move pattern pointer: i = (1+1)%3 = 2

Step 6:

  • Current character: word[2] = 'c'
  • Expected: s[2] = 'c'
  • Since 'c' = 'c', it matches!
  • j = 3 (exit loop as j = n)

Handle Ending:

  • Last character is 'c', so no additional insertions needed

Result: ans = 3

The transformed string would be "abcabc" where bold letters are insertions, forming two complete "abc" patterns.

Solution Implementation

1class Solution:
2    def addMinimum(self, word: str) -> int:
3        # Define the target pattern that should repeat
4        pattern = 'abc'
5      
6        # Initialize variables
7        additions_needed = 0  # Count of characters we need to add
8        word_length = len(word)
9      
10        # Pointers for traversing word and pattern
11        word_index = 0  # Current position in the input word
12        pattern_index = 0  # Current position in the pattern "abc"
13      
14        # Process each character position in the pattern
15        while word_index < word_length:
16            # If current word character doesn't match expected pattern character
17            if word[word_index] != pattern[pattern_index]:
18                # We need to add the missing character
19                additions_needed += 1
20            else:
21                # Characters match, move to next character in word
22                word_index += 1
23          
24            # Move to next position in pattern (cycling through 'a', 'b', 'c')
25            pattern_index = (pattern_index + 1) % 3
26      
27        # Handle incomplete pattern at the end
28        # If the last character isn't 'c', we need to complete the pattern
29        if word[-1] != 'c':
30            if word[-1] == 'b':
31                # If last char is 'b', we only need to add 'c'
32                additions_needed += 1
33            else:
34                # If last char is 'a', we need to add 'b' and 'c'
35                additions_needed += 2
36      
37        return additions_needed
38
1class Solution {
2    public int addMinimum(String word) {
3        // Target pattern to match
4        String pattern = "abc";
5      
6        // Counter for minimum additions needed
7        int additionsNeeded = 0;
8        int wordLength = word.length();
9      
10        // Two pointers: patternIndex cycles through "abc", wordIndex traverses the input word
11        int patternIndex = 0;
12        int wordIndex = 0;
13      
14        // Process each character in the word
15        while (wordIndex < wordLength) {
16            // Check if current character matches expected pattern character
17            if (word.charAt(wordIndex) != pattern.charAt(patternIndex)) {
18                // Mismatch: need to add the missing character
19                additionsNeeded++;
20            } else {
21                // Match found: move to next character in word
22                wordIndex++;
23            }
24          
25            // Move to next position in pattern (cycling through a->b->c->a...)
26            patternIndex = (patternIndex + 1) % 3;
27        }
28      
29        // Handle incomplete pattern at the end
30        // If last character is not 'c', we need to complete the "abc" pattern
31        char lastChar = word.charAt(wordLength - 1);
32        if (lastChar != 'c') {
33            // If last char is 'b', add 1 for missing 'c'
34            // If last char is 'a', add 2 for missing 'b' and 'c'
35            additionsNeeded += (lastChar == 'b') ? 1 : 2;
36        }
37      
38        return additionsNeeded;
39    }
40}
41
1class Solution {
2public:
3    int addMinimum(string word) {
4        // Pattern string to match against
5        string pattern = "abc";
6      
7        // Initialize the count of characters to add
8        int charactersToAdd = 0;
9        int wordLength = word.size();
10      
11        // Iterate through the word and pattern simultaneously
12        // patternIndex cycles through 0, 1, 2 (for 'a', 'b', 'c')
13        // wordIndex traverses through the input word
14        int patternIndex = 0;
15        int wordIndex = 0;
16      
17        while (wordIndex < wordLength) {
18            // If current character doesn't match the expected pattern character
19            if (word[wordIndex] != pattern[patternIndex]) {
20                // We need to insert the missing character
21                charactersToAdd++;
22            } else {
23                // Characters match, move to next character in word
24                wordIndex++;
25            }
26          
27            // Move to next position in pattern (cycling through a->b->c->a...)
28            patternIndex = (patternIndex + 1) % 3;
29        }
30      
31        // Handle the ending: ensure the last group ends with 'c'
32        // If the last character is not 'c', we need to add missing characters
33        if (word[wordLength - 1] != 'c') {
34            // If last character is 'b', we only need to add 'c'
35            // If last character is 'a', we need to add 'b' and 'c'
36            charactersToAdd += (word[wordLength - 1] == 'b') ? 1 : 2;
37        }
38      
39        return charactersToAdd;
40    }
41};
42
1/**
2 * Calculates the minimum number of characters needed to be added to make the word
3 * consist of complete "abc" patterns
4 * @param word - The input string to process
5 * @returns The minimum number of characters to add
6 */
7function addMinimum(word: string): number {
8    // Target pattern to match
9    const targetPattern: string = 'abc';
10  
11    // Counter for characters that need to be added
12    let charactersToAdd: number = 0;
13  
14    // Length of the input word
15    const wordLength: number = word.length;
16  
17    // Iterate through the word and pattern simultaneously
18    // patternIndex cycles through 0, 1, 2 (corresponding to 'a', 'b', 'c')
19    // wordIndex tracks our position in the input word
20    let patternIndex: number = 0;
21    let wordIndex: number = 0;
22  
23    while (wordIndex < wordLength) {
24        // If current character doesn't match expected pattern character
25        if (word[wordIndex] !== targetPattern[patternIndex]) {
26            // We need to add the missing character
27            charactersToAdd++;
28        } else {
29            // Character matches, move to next character in word
30            wordIndex++;
31        }
32      
33        // Move to next position in pattern (cycling through 'a', 'b', 'c')
34        patternIndex = (patternIndex + 1) % 3;
35    }
36  
37    // Handle trailing characters needed after the last character of word
38    const lastCharacter: string = word[wordLength - 1];
39  
40    if (lastCharacter === 'b') {
41        // If word ends with 'b', we need to add 'c' to complete the pattern
42        charactersToAdd++;
43    } else if (lastCharacter === 'a') {
44        // If word ends with 'a', we need to add 'bc' to complete the pattern
45        charactersToAdd += 2;
46    }
47    // If word ends with 'c', the pattern is complete, no additions needed
48  
49    return charactersToAdd;
50}
51

Time and Space Complexity

The time complexity is O(n), where n is the length of the string word. The algorithm uses a single while loop that iterates through each character of the input string exactly once. The variable j starts at 0 and increments only when word[j] == s[i], continuing until j < n is false. Each iteration performs constant time operations (comparisons, increments, and modulo operation), resulting in linear time complexity.

The space complexity is O(1). The algorithm uses only a fixed amount of extra space regardless of the input size. The variables used are: s (a constant string "abc"), ans (counter for additions), n (length of input), i and j (two pointers). Since these variables don't scale with the input size, the space complexity is constant.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

Pitfall 1: Incorrectly Handling Pattern Completion at the End

The Problem: The most common mistake is forgetting to complete the final "abc" pattern after processing all characters. Many developers assume that once all characters in word are processed, the algorithm is complete. However, if the string doesn't end with 'c', we have an incomplete pattern that needs additional characters.

Example of the Bug:

# Incorrect implementation - missing end pattern completion
def addMinimum(word: str) -> int:
    pattern = 'abc'
    additions_needed = 0
    word_index = 0
    pattern_index = 0
  
    while word_index < len(word):
        if word[word_index] != pattern[pattern_index]:
            additions_needed += 1
        else:
            word_index += 1
        pattern_index = (pattern_index + 1) % 3
  
    return additions_needed  # Bug: doesn't handle incomplete pattern at end

Why It Fails: For input word = "aab":

  • The algorithm processes: 'a' matches, 'a' doesn't match 'b' (add 1), 'b' matches
  • It returns 1, but the correct answer is 3
  • The final state has an incomplete "abc" pattern ending at 'b', requiring 'c' to be added

The Solution: Always check the last character of the word and determine how many characters are needed to complete the pattern:

# After the main loop
if word[-1] != 'c':
    if word[-1] == 'b':
        additions_needed += 1  # Need to add 'c'
    else:  # word[-1] == 'a'
        additions_needed += 2  # Need to add 'b' and 'c'

Pitfall 2: Misunderstanding the Pattern Cycling Logic

The Problem: Some developers might incorrectly reset the pattern index or handle the cycling through 'abc' improperly, especially when characters don't match.

Example of the Bug:

# Incorrect - resetting pattern_index when mismatch occurs
while word_index < len(word):
    if word[word_index] != pattern[pattern_index]:
        additions_needed += 1
        pattern_index = 0  # Bug: shouldn't reset to 0
    else:
        word_index += 1
        pattern_index = (pattern_index + 1) % 3

Why It Fails: The pattern index should always advance regardless of whether we match or insert a character. The pattern "abc" must be maintained in order. If we're expecting 'b' and see 'c', we insert 'b' and then move to expecting 'c' (not back to 'a').

The Solution: Always advance the pattern index by 1 (with modulo 3) regardless of match/mismatch:

while word_index < len(word):
    if word[word_index] != pattern[pattern_index]:
        additions_needed += 1
    else:
        word_index += 1
    pattern_index = (pattern_index + 1) % 3  # Always advance
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

You are given an array of intervals where intervals[i] = [start_i, end_i] represent the start and end of the ith interval. You need to merge all overlapping intervals and return an array of the non-overlapping intervals that cover all the intervals in the input.


Recommended Readings

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

Load More