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.
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]
wheres = "abc"
, we move to the next character inword
- 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 throughword
Main Loop:
While j < n
(where n
is the length of word
):
-
Check if
word[j]
matchess[i]
:- If
word[j] != s[i]
: We need to insert the missing characters[i]
, so incrementans += 1
. Keepj
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. Movej
forward by 1 to process the next character inword
.
- If
-
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 toans
- If
word[-1] == 'a'
: We need to add 'b' and 'c' to complete the pattern, so add 2 toans
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 EvaluatorExample 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
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
Stack Intro Imagine you have a pile of books on your desk If you want to add a new book you place it on top If you want to read a book you take it from the top And if you simply want to see which book is on the top you
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Want a Structured Path to Master System Design Too? Don’t Miss This!