Facebook Pixel

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:

  1. The string must have exactly a + b characters in total
  2. The string must contain exactly a occurrences of the letter 'a'
  3. The string must contain exactly b occurrences of the letter 'b'
  4. The string cannot contain three consecutive 'a' characters (no "aaa" substring)
  5. The string cannot contain three consecutive 'b' characters (no "bbb" substring)

For example:

  • If a = 1 and b = 2, a valid output could be "abb" or "bab" or "bba"
  • If a = 4 and b = 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.

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

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:

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

  2. When b > a: Similarly, we use "bba" to consume more 'b's relative to 'a's.

  3. 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", now a = 3, b = 1
  • Second iteration: a > b, append "aab", now a = 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:

  1. Initialize an empty list ans to collect string segments. Using a list instead of string concatenation is more efficient in Python.

  2. Main loop - while both a and b are non-zero:

    • Case 1: a > b
      • Append "aab" to the result list
      • Decrement a by 2 and b by 1
    • Case 2: b > a
      • Append "bba" to the result list
      • Decrement a by 1 and b by 2
    • Case 3: a == b
      • Append "ab" to the result list
      • Decrement both a and b by 1
  3. 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)
  4. 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 Evaluator

Example 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 exits
  • a = 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 reduce a by 2 and b by 1
  • When b > a, we reduce b by 2 and a 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
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which data structure is used to implement priority queue?


Recommended Readings

Want a Structured Path to Master System Design Too? Donโ€™t Miss This!

Load More