Given a string word
to which you can insert letters "a", "b" or "c" anywhere and any number of times, return the minimum number of letters that must be inserted so that word
becomes valid.
A string is called valid if it can be formed by concatenating the string "abc" several times.
For example:
word = "b"
, the output is 2. We can insert the letter "a" right before "b", and the letter "c" right next to "b" to obtain the valid string "abc".word = "aaa"
, the output is 6. We can insert letters "b" and "c" next to each "a" to obtain the valid string "abcabcabc".word = "abc"
, the output is 0. The word is already valid, no modifications are needed.Constraints:
1 <= word.length <= 50
word
consists of letters "a", "b" and "c" only.The most straightforward approach is to consider all possible insertion combinations and check if the resulting string is a valid concatenation of "abc". This involves recursively trying to insert 'a', 'b', or 'c' at every possible position in the word and checking if the modified word is valid. A word is considered valid if it is composed entirely of repeating "abc" subsequences.
However, this approach is highly inefficient due to the exponential number of possible combinations of inserted characters.
Code (Python):
def is_valid(s):
if not s:
return True
if len(s) % 3 != 0:
return False
for i in range(0, len(s), 3):
if s[i:i+3] != "abc":
return False
return True
def min_insertions_naive(word):
def solve(current_word, insertions):
if is_valid(current_word):
return insertions
if len(current_word) > 50 * 3: # Optimization: Limit recursion depth
return float('inf')
min_inserts = float('inf')
for i in range(len(current_word) + 1):
min_inserts = min(min_inserts, solve(current_word[:i] + 'a' + current_word[i:], insertions + 1))
min_inserts = min(min_inserts, solve(current_word[:i] + 'b' + current_word[i:], insertions + 1))
min_inserts = min(min_inserts, solve(current_word[:i] + 'c' + current_word[i:], insertions + 1))
return min_inserts
return solve(word, 0)
A better approach is to use dynamic programming. We can define dp[i][j]
as the minimum number of insertions needed to make the first i
characters of word
a valid prefix ending with the j
-th character of "abc".
j = 0
: The prefix ends with 'a'.j = 1
: The prefix ends with 'b'.j = 2
: The prefix ends with 'c'.Base Case: dp[0][0] = 0
(empty string needs no insertion to end with 'a'). dp[0][1] = dp[0][2] = float('inf')
Transitions:
word[i-1] == 'a'
: dp[i][0] = dp[i-1][0]
Else: dp[i][0] = min(dp[i-1][0], dp[i-1][1], dp[i-1][2]) + 1
word[i-1] == 'b'
: dp[i][1] = dp[i-1][0]
Else: dp[i][1] = dp[i-1][0] + 1
word[i-1] == 'c'
: dp[i][2] = dp[i-1][1]
Else: dp[i][2] = dp[i-1][1] + 1
The answer will be dp[n][2]
, where n
is the length of word
. If dp[n][2]
is still infinity, then the only other way to make the entire string valid is to pad the word with "abc" chunks until we get a valid string. The padding will be equal to len(word)
+ 2 - (len(word) % 3 )
Edge Cases:
word
: Requires 0 insertion. We should account for cases where length word % 3 != 0
Code (Python):
def min_insertions(word):
n = len(word)
dp = [[float('inf')] * 3 for _ in range(n + 1)]
dp[0][0] = 0
for i in range(1, n + 1):
if word[i - 1] == 'a':
dp[i][0] = dp[i - 1][0]
else:
dp[i][0] = min(dp[i - 1][0], dp[i - 1][1], dp[i - 1][2]) + 1
if word[i - 1] == 'b':
dp[i][1] = dp[i - 1][0]
else:
dp[i][1] = dp[i - 1][0] + 1
if word[i - 1] == 'c':
dp[i][2] = dp[i - 1][1]
else:
dp[i][2] = dp[i - 1][1] + 1
if dp[n][2] != float('inf'):
return dp[n][2]
else:
return n + (2 - (n % 3)) % 3