984. String Without AAA or BBB
Problem Description
You need to create a string using only the characters 'a' and 'b' with specific constraints.
Given two integers a
and b
, construct a string s
that satisfies all of the following conditions:
- The string must have exactly
a + b
characters in total - The string must contain exactly
a
occurrences of the letter 'a' - The string must contain exactly
b
occurrences of the letter 'b' - The string cannot contain three consecutive 'a' characters (no "aaa" substring)
- The string cannot contain three consecutive 'b' characters (no "bbb" substring)
For example:
- If
a = 1
andb = 2
, a valid output could be"abb"
or"bab"
or"bba"
- If
a = 4
andb = 1
, a valid output could be"aabaa"
The problem guarantees that a valid solution always exists for the given inputs, and you can return any valid string that meets these requirements.
Intuition
The key insight is to maintain balance between 'a' and 'b' characters while avoiding three consecutive identical characters. Think of it like distributing two types of items where you can't place more than two of the same type together.
When we have more of one character than the other, we should use two of the more frequent character and one of the less frequent character in a pattern. This helps us reduce the imbalance quickly while staying within the constraint.
Consider these scenarios:
-
When
a > b
: We have more 'a's to place. Using the pattern"aab"
allows us to consume two 'a's while only using one 'b'. This prevents running out of 'b's too quickly and helps balance the counts. -
When
b > a
: Similarly, we use"bba"
to consume more 'b's relative to 'a's. -
When
a == b
: We can simply alternate with"ab"
or"ba"
since we have equal amounts to distribute.
The greedy approach works because:
- By always placing two of the more frequent character, we're maximizing the reduction of the imbalance
- We never risk creating three consecutive characters since we switch after placing at most two
- When one character runs out, the remaining characters (if any) will be at most 2, which can be safely appended at the end
For example, if a = 5
and b = 2
:
- First iteration:
a > b
, append"aab"
, nowa = 3
,b = 1
- Second iteration:
a > b
, append"aab"
, nowa = 1
,b = 0
- No more 'b's left, append remaining
"a"
- Result:
"aabaaba"
This strategy ensures we never violate the constraint while efficiently using up both characters.
Learn more about Greedy patterns.
Solution Approach
The implementation uses a greedy algorithm with a simple list to build the result string efficiently.
Algorithm Steps:
-
Initialize an empty list
ans
to collect string segments. Using a list instead of string concatenation is more efficient in Python. -
Main loop - while both
a
andb
are non-zero:- Case 1:
a > b
- Append
"aab"
to the result list - Decrement
a
by 2 andb
by 1
- Append
- Case 2:
b > a
- Append
"bba"
to the result list - Decrement
a
by 1 andb
by 2
- Append
- Case 3:
a == b
- Append
"ab"
to the result list - Decrement both
a
andb
by 1
- Append
- Case 1:
-
Handle remaining characters:
- After the loop, at most one type of character remains
- If
a > 0
: append'a' * a
(will be at most 2 characters) - If
b > 0
: append'b' * b
(will be at most 2 characters)
-
Join and return: Convert the list of strings to a single string using
''.join(ans)
Why this works:
- The loop continues until one character type is exhausted
- In each iteration, we place 2 of the more frequent character and 1 of the less frequent (or 1 of each when equal)
- This ensures we never have more than 2 consecutive identical characters in any segment
- When one character runs out, the other can have at most 2 remaining (proven by the fact that we always reduce the larger count by 2 when there's an imbalance)
Time Complexity: O(a + b)
- we process characters proportional to the total count
Space Complexity: O(a + b)
- for storing the result string
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the algorithm with a = 7
and b = 3
:
Initial state: a = 7
, b = 3
, ans = []
Iteration 1:
- Check:
a (7) > b (3)
, so we append"aab"
- Update:
a = 7 - 2 = 5
,b = 3 - 1 = 2
- Result so far:
ans = ["aab"]
Iteration 2:
- Check:
a (5) > b (2)
, so we append"aab"
- Update:
a = 5 - 2 = 3
,b = 2 - 1 = 1
- Result so far:
ans = ["aab", "aab"]
Iteration 3:
- Check:
a (3) > b (1)
, so we append"aab"
- Update:
a = 3 - 2 = 1
,b = 1 - 1 = 0
- Result so far:
ans = ["aab", "aab", "aab"]
After loop:
b = 0
, so the loop exitsa = 1
remains, so we append"a"
- Final:
ans = ["aab", "aab", "aab", "a"]
Join and return: "".join(ans)
= "aabaabaa"
Verification:
- Total length: 10 characters โ (7 + 3)
- Count of 'a': 7 โ
- Count of 'b': 3 โ
- No "aaa" substring โ
- No "bbb" substring โ
Notice how the algorithm naturally prevents three consecutive characters by switching to 'b' after every two 'a's, and the remaining single 'a' at the end doesn't create a violation since it follows a 'b'.
Solution Implementation
1class Solution:
2 def strWithout3a3b(self, a: int, b: int) -> str:
3 """
4 Generate a string with 'a' occurring a times and 'b' occurring b times,
5 ensuring no three consecutive identical characters appear.
6
7 Args:
8 a: Number of 'a' characters to include
9 b: Number of 'b' characters to include
10
11 Returns:
12 A valid string containing exactly a 'a's and b 'b's without 3 consecutive same characters
13 """
14 result = []
15
16 # Process while we have both 'a' and 'b' characters remaining
17 while a > 0 and b > 0:
18 if a > b:
19 # When we have more 'a's, use pattern 'aab' to consume more 'a's
20 result.append('aab')
21 a -= 2
22 b -= 1
23 elif a < b:
24 # When we have more 'b's, use pattern 'bba' to consume more 'b's
25 result.append('bba')
26 a -= 1
27 b -= 2
28 else:
29 # When counts are equal, use simple alternating pattern 'ab'
30 result.append('ab')
31 a -= 1
32 b -= 1
33
34 # Append any remaining 'a' characters (will be at most 2)
35 if a > 0:
36 result.append('a' * a)
37
38 # Append any remaining 'b' characters (will be at most 2)
39 if b > 0:
40 result.append('b' * b)
41
42 # Join all parts into final string
43 return ''.join(result)
44
1class Solution {
2 /**
3 * Generate a string containing 'a' and 'b' characters without 3 consecutive same characters
4 * @param a - count of 'a' characters to include
5 * @param b - count of 'b' characters to include
6 * @return a valid string with given counts of 'a' and 'b' without "aaa" or "bbb"
7 */
8 public String strWithout3a3b(int a, int b) {
9 StringBuilder result = new StringBuilder();
10
11 // Process while both counts are positive
12 while (a > 0 && b > 0) {
13 if (a > b) {
14 // When 'a' count is greater, use two 'a's and one 'b'
15 result.append("aab");
16 a -= 2;
17 b -= 1;
18 } else if (a < b) {
19 // When 'b' count is greater, use two 'b's and one 'a'
20 result.append("bba");
21 a -= 1;
22 b -= 2;
23 } else {
24 // When counts are equal, alternate between 'a' and 'b'
25 result.append("ab");
26 a--;
27 b--;
28 }
29 }
30
31 // Append remaining 'a' characters if any
32 if (a > 0) {
33 result.append("a".repeat(a));
34 }
35
36 // Append remaining 'b' characters if any
37 if (b > 0) {
38 result.append("b".repeat(b));
39 }
40
41 return result.toString();
42 }
43}
44
1class Solution {
2public:
3 string strWithout3a3b(int a, int b) {
4 string result;
5
6 // Process while both counts are non-zero
7 while (a > 0 && b > 0) {
8 if (a > b) {
9 // When 'a' count is greater, add "aab" pattern
10 // This uses more 'a's to balance the counts
11 result += "aab";
12 a -= 2;
13 b -= 1;
14 } else if (a < b) {
15 // When 'b' count is greater, add "bba" pattern
16 // This uses more 'b's to balance the counts
17 result += "bba";
18 a -= 1;
19 b -= 2;
20 } else {
21 // When counts are equal, add "ab" pattern
22 result += "ab";
23 a--;
24 b--;
25 }
26 }
27
28 // Append remaining 'a's if any
29 if (a > 0) {
30 result += string(a, 'a');
31 }
32
33 // Append remaining 'b's if any
34 if (b > 0) {
35 result += string(b, 'b');
36 }
37
38 return result;
39 }
40};
41
1function strWithout3a3b(a: number, b: number): string {
2 let result: string = "";
3
4 // Process while both counts are non-zero
5 while (a > 0 && b > 0) {
6 if (a > b) {
7 // When 'a' count is greater, add "aab" pattern
8 // This uses more 'a's to balance the counts
9 result += "aab";
10 a -= 2;
11 b -= 1;
12 } else if (a < b) {
13 // When 'b' count is greater, add "bba" pattern
14 // This uses more 'b's to balance the counts
15 result += "bba";
16 a -= 1;
17 b -= 2;
18 } else {
19 // When counts are equal, add "ab" pattern
20 result += "ab";
21 a--;
22 b--;
23 }
24 }
25
26 // Append remaining 'a's if any
27 if (a > 0) {
28 result += "a".repeat(a);
29 }
30
31 // Append remaining 'b's if any
32 if (b > 0) {
33 result += "b".repeat(b);
34 }
35
36 return result;
37}
38
Time and Space Complexity
Time Complexity: O(a + b)
The algorithm uses a while loop that continues as long as both a
and b
are non-zero. In each iteration, at least one of the counters decreases by 1, and at most the sum decreases by 3. The loop runs at most min(a, b)
times, which is bounded by (a + b) / 2
. After the loop, we may append remaining characters, which takes O(a)
or O(b)
time. The final join
operation iterates through all elements in the list, which contains O(a + b)
characters in total. Therefore, the overall time complexity is O(a + b)
.
Space Complexity: O(a + b)
The algorithm creates a list ans
to store string segments. In the worst case, the list contains approximately (a + b) / 3
elements (when we append 3 characters per iteration), but the total number of characters stored is exactly a + b
. The final string returned by ''.join(ans)
also requires O(a + b)
space. Therefore, the total space complexity is O(a + b)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Remainder Handling
Pitfall: A common mistake is assuming that after the main loop, we can simply append all remaining characters without checking the count. This could lead to violations of the "no three consecutive" rule.
Example of incorrect approach:
# WRONG - could create "aaa" or "bbb" while a > 0 and b > 0: # ... main loop logic ... # Dangerous: directly appending all remaining return ''.join(result) + 'a' * a + 'b' * b
Why the current solution avoids this: The greedy algorithm ensures that when the loop ends, at most 2 characters of one type remain. This is because:
- When
a > b
, we reducea
by 2 andb
by 1 - When
b > a
, we reduceb
by 2 anda
by 1 - The difference between counts can never increase beyond 2
2. String Concatenation Performance
Pitfall: Building the result using string concatenation in a loop:
# INEFFICIENT - O(nยฒ) time complexity result = "" while a > 0 and b > 0: if a > b: result += "aab" # String concatenation creates new string each time # ...
Solution: Use a list to collect segments and join at the end:
# EFFICIENT - O(n) time complexity result = [] while a > 0 and b > 0: if a > b: result.append("aab") # List append is O(1) # ... return ''.join(result) # Single join operation at the end
3. Off-by-One Errors in Decrementing
Pitfall: Incorrectly decrementing counters, especially when placing two characters:
# WRONG - might not consume enough characters if a > b: result.append('aab') a -= 1 # Should be a -= 2 b -= 1
Solution: Carefully track how many of each character you're placing:
- When appending "aab": decrement
a
by 2,b
by 1 - When appending "bba": decrement
b
by 2,a
by 1 - When appending "ab": decrement both by 1
4. Not Handling Edge Cases Early
Pitfall: Not considering when one counter starts at 0:
# Missing edge case handling
def strWithout3a3b(self, a: int, b: int) -> str:
result = []
while a > 0 and b > 0: # What if a=0 or b=0 initially?
# ...
Solution: The current implementation naturally handles this because:
- The while loop condition
a > 0 and b > 0
immediately exits if either is 0 - The remainder handling after the loop correctly appends any non-zero count
Which data structure is used to implement priority queue?
Recommended Readings
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
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!