2914. Minimum Number of Changes to Make Binary String Beautiful
Problem Description
You have a binary string s
(containing only '0's and '1's) with an even length, and it's 0-indexed.
A string is considered beautiful when you can split it into one or more parts where:
- Every part has an even length (2, 4, 6, etc.)
- Every part contains only '0's or only '1's (all characters in a part must be the same)
For example:
"0000"
is beautiful (one part with all 0's, length 4)"1100"
is beautiful (two parts:"11"
and"00"
, each length 2)"0101"
is NOT beautiful as-is (would need parts like"01"
which has mixed characters)
You can change any character in the string from '0' to '1' or from '1' to '0'.
The task is to find the minimum number of changes needed to make the string beautiful.
The solution recognizes that to minimize changes, we should partition the string into the smallest possible even-length substrings (length 2). For each pair of adjacent characters at positions [0,1]
, [2,3]
, [4,5]
, etc., we need both characters in the pair to be the same. If s[i-1]
and s[i]
(where i
is odd) are different, we need to change one of them. The code counts these mismatches by checking all odd indices against their preceding even indices.
Intuition
To make the string beautiful, we need to partition it into substrings where each substring has even length and contains only identical characters.
The key insight is: what's the optimal way to partition the string to minimize changes?
Consider that we have flexibility in choosing partition sizes - we could use substrings of length 2, 4, 6, etc. However, using larger partitions means more characters need to be identical, which potentially requires more changes.
For example, if we have "0110"
:
- Partitioning as one substring of length 4 would require all 4 characters to be the same, needing at least 2 changes
- Partitioning as two substrings of length 2 (
"01"
and"10"
) would require only 2 changes total (one per pair)
This leads us to realize that using the smallest possible partition size (length 2) gives us the most flexibility and minimizes changes. With length-2 partitions, we only need to ensure pairs of adjacent characters are identical.
So the problem simplifies to: divide the string into consecutive pairs [s[0], s[1]]
, [s[2], s[3]]
, [s[4], s[5]]
, etc., and for each pair, make both characters the same.
For any pair where the two characters differ, we need exactly one change (we can change either character to match the other). If they're already the same, no change is needed.
Therefore, we just need to count how many pairs have different characters. This is why the solution checks positions 1, 3, 5, ... (all odd indices) against their preceding positions 0, 2, 4, ... (even indices) and counts the mismatches.
Solution Approach
The implementation follows a straightforward counting approach based on our intuition that we should partition the string into pairs of length 2.
Algorithm Steps:
-
Traverse odd indices: We iterate through positions
1, 3, 5, ...
up tolen(s) - 1
usingrange(1, len(s), 2)
. This gives us the second position of each pair. -
Check each pair: For each odd index
i
, we compares[i]
withs[i-1]
(the character at the previous even index). These two characters form a pair that needs to be identical in the beautiful string. -
Count mismatches: If
s[i] != s[i-1]
, the pair has different characters and needs one change. We add 1 to our count. -
Return the total: The sum of all mismatches gives us the minimum number of changes needed.
Implementation Details:
The solution uses a generator expression with sum()
to count mismatches in a single line:
sum(s[i] != s[i - 1] for i in range(1, len(s), 2))
range(1, len(s), 2)
generates odd indices: 1, 3, 5, ...s[i] != s[i - 1]
evaluates toTrue
(counted as 1) when characters differ,False
(counted as 0) when they're the samesum()
adds up all theTrue
values to get the total number of required changes
Time Complexity: O(n)
where n
is the length of the string, as we visit each odd index once.
Space Complexity: O(1)
as we only use a constant amount of extra space for the counting.
This greedy approach works because each pair is independent - changing a character in one pair doesn't affect the decision for any other pair.
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 the string s = "10110010"
.
Step 1: Identify the pairs We divide the string into consecutive pairs of length 2:
- Pair 1:
s[0:2]
="10"
(positions 0 and 1) - Pair 2:
s[2:4]
="11"
(positions 2 and 3) - Pair 3:
s[4:6]
="00"
(positions 4 and 5) - Pair 4:
s[6:8]
="10"
(positions 6 and 7)
Step 2: Check each pair for mismatches We iterate through odd indices (1, 3, 5, 7) and compare with the previous even index:
- Index 1: Compare
s[1]='0'
withs[0]='1'
→ Different → Need 1 change - Index 3: Compare
s[3]='1'
withs[2]='1'
→ Same → No change needed - Index 5: Compare
s[5]='0'
withs[4]='0'
→ Same → No change needed - Index 7: Compare
s[7]='0'
withs[6]='1'
→ Different → Need 1 change
Step 3: Count total changes Total mismatches = 2 (at pairs 1 and 4)
Step 4: Apply changes to verify We need 2 changes minimum. For example:
- Change
s[0]
from'1'
to'0'
→ Pair 1 becomes"00"
- Change
s[6]
from'1'
to'0'
→ Pair 4 becomes"00"
Result: "00110000"
which is beautiful (splits into "00"
, "11"
, "00"
, "00"
)
The algorithm correctly identifies that we need 2 changes to make the string beautiful.
Solution Implementation
1class Solution:
2 def minChanges(self, s: str) -> int:
3 """
4 Calculate the minimum number of changes needed to make the string
5 consist of identical pairs of consecutive characters.
6
7 The string is divided into pairs: (s[0], s[1]), (s[2], s[3]), etc.
8 For each pair, if the two characters are different, one change is needed.
9
10 Args:
11 s: Input string to be processed
12
13 Returns:
14 Minimum number of character changes required
15 """
16 # Count mismatches within each pair by checking odd-indexed positions
17 # against their preceding even-indexed positions
18 # range(1, len(s), 2) generates indices: 1, 3, 5, ...
19 return sum(s[i] != s[i - 1] for i in range(1, len(s), 2))
20
1class Solution {
2 /**
3 * Calculates the minimum number of changes needed to make the string
4 * have identical characters in each consecutive pair.
5 * The string is divided into pairs: (s[0], s[1]), (s[2], s[3]), etc.
6 *
7 * @param s the input string to process
8 * @return the minimum number of character changes required
9 */
10 public int minChanges(String s) {
11 int changeCount = 0;
12
13 // Iterate through the string by checking pairs of characters
14 // Start from index 1 and increment by 2 to check each pair
15 for (int i = 1; i < s.length(); i += 2) {
16 // Check if the current character differs from its pair (previous character)
17 if (s.charAt(i) != s.charAt(i - 1)) {
18 // Increment the count if characters in the pair are different
19 changeCount++;
20 }
21 }
22
23 return changeCount;
24 }
25}
26
1class Solution {
2public:
3 int minChanges(string s) {
4 int changeCount = 0;
5 int stringLength = s.size();
6
7 // Iterate through the string in pairs (checking positions 1, 3, 5, ...)
8 // Each pair consists of characters at positions (i-1, i)
9 for (int i = 1; i < stringLength; i += 2) {
10 // If the two characters in the current pair are different,
11 // we need one change to make them the same
12 if (s[i] != s[i - 1]) {
13 changeCount++;
14 }
15 }
16
17 return changeCount;
18 }
19};
20
1/**
2 * Calculates the minimum number of changes needed to make the string
3 * have identical characters at consecutive even-odd position pairs.
4 *
5 * The algorithm processes the string in pairs (positions 0-1, 2-3, 4-5, etc.)
6 * and counts how many pairs have different characters.
7 *
8 * @param s - The input string to process
9 * @returns The minimum number of character changes needed
10 */
11function minChanges(s: string): number {
12 // Initialize counter for required changes
13 let changeCount: number = 0;
14
15 // Iterate through odd positions (1, 3, 5, ...) to check pairs
16 for (let i: number = 1; i < s.length; i += 2) {
17 // Check if current character differs from its preceding character
18 // If they differ, we need one change to make them identical
19 if (s[i] !== s[i - 1]) {
20 changeCount++;
21 }
22 }
23
24 // Return the total number of changes needed
25 return changeCount;
26}
27
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the string s
. The algorithm iterates through the string once using a range that steps through odd indices from 1 to len(s)
with step size 2, which means it performs approximately n/2
iterations. Each iteration performs a constant-time comparison operation (s[i] != s[i-1]
). Therefore, the overall time complexity is O(n/2)
which simplifies to O(n)
.
The space complexity is O(1)
. The algorithm uses only a constant amount of extra space regardless of the input size. The sum()
function with a generator expression doesn't create an intermediate list but instead evaluates the comparisons one at a time and accumulates the result in a single variable. No additional data structures are created that scale with the input size.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Misunderstanding the Problem - Trying to Find Optimal Partition Sizes
The Mistake: Many people initially think they need to find the optimal way to partition the string into variable-length segments (like choosing between length 2, 4, 6, etc.) to minimize changes. They might try dynamic programming or complex algorithms to determine whether grouping as "0000" (length 4) or "00" + "00" (two length-2 parts) would be better.
Why It Happens: The problem statement mentions that parts can have any even length (2, 4, 6, etc.), which suggests there might be an optimization problem in choosing partition sizes.
The Reality: Always partitioning into length-2 segments gives the optimal result. Here's why:
- If we have a segment of length 4 like "0101", we need 2 changes to make it all same
- If we split it into two length-2 segments "01" and "01", we still need 2 changes (one per pair)
- The number of changes required is the same regardless of how we group the pairs
- Therefore, the simplest approach (always using length 2) is optimal
Pitfall 2: Off-by-One Errors in Index Handling
The Mistake:
# Wrong: Starting from index 0
return sum(s[i] != s[i + 1] for i in range(0, len(s), 2))
# Wrong: Going beyond bounds
return sum(s[i] != s[i - 1] for i in range(1, len(s)))
Why It Happens: Confusion about whether to iterate through even or odd indices, and which direction to compare (i with i+1 vs i with i-1).
The Correct Approach:
- Use
range(1, len(s), 2)
to get odd indices (1, 3, 5, ...) - Compare each odd index with the previous even index:
s[i] != s[i-1]
- This ensures we check each pair exactly once without index errors
Pitfall 3: Overthinking Which Character to Change
The Mistake: Trying to determine which specific character in a mismatched pair should be changed to '0' or '1', perhaps considering the surrounding context or trying to create longer uniform segments.
Why It Happens: The problem asks for the minimum number of changes, which might lead to thinking we need to specify exactly which changes to make.
The Reality:
- We only need to count the number of changes, not specify which ones
- For each mismatched pair, exactly one change is needed regardless of which character we change
- The choice doesn't affect other pairs since each pair is independent
Pitfall 4: Forgetting Edge Cases
The Mistake: Not considering or testing edge cases properly:
# Might fail on empty string or single character (though problem guarantees even length)
def minChanges(self, s: str) -> int:
if not s: # Unnecessary check given problem constraints
return 0
return sum(s[i] != s[i - 1] for i in range(1, len(s), 2))
The Reality: The problem guarantees the string has even length, so we don't need special handling for odd lengths or empty strings. However, it's good practice to verify the smallest valid input (length 2) works correctly.
Solution to Avoid These Pitfalls:
- Understand the key insight: Each pair of consecutive characters is independent, and we always want pairs of identical characters
- Use clear variable names and comments if expanding the one-liner:
def minChanges(self, s: str) -> int:
changes_needed = 0
# Process each pair: (0,1), (2,3), (4,5), etc.
for pair_start in range(0, len(s), 2):
first_char = s[pair_start]
second_char = s[pair_start + 1]
if first_char != second_char:
changes_needed += 1
return changes_needed
- Test with simple examples to verify understanding:
- "01" → 1 change needed
- "0011" → 0 changes needed
- "0101" → 2 changes needed
What is an advantages of top-down dynamic programming vs bottom-up dynamic programming?
Recommended Readings
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
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!