2712. Minimum Cost to Make All Characters Equal
Problem Description
You have a binary string s
(containing only '0's and '1's) that is 0-indexed with length n
. Your goal is to make all characters in the string equal (either all '0's or all '1's) using the minimum total cost.
You can perform two types of operations:
-
Left-side inversion: Choose an index
i
and invert all characters from index0
to indexi
(inclusive). This operation costsi + 1
. -
Right-side inversion: Choose an index
i
and invert all characters from indexi
to indexn - 1
(inclusive). This operation costsn - i
.
When you invert a character, '0' becomes '1' and '1' becomes '0'.
The key insight is that whenever two adjacent characters are different (s[i] ≠ s[i-1]
), you must perform an operation to make them equal. At each such position, you have two choices:
- Invert the left portion
s[0..i-1]
with costi
- Invert the right portion
s[i..n-1]
with costn - i
The solution greedily chooses the minimum cost option at each position where adjacent characters differ, accumulating these costs to find the minimum total cost needed to make all characters equal.
For example, if s = "0011"
, the characters at positions 1 and 2 are different, so you need an operation there. You can either invert s[0..1]
with cost 2, or invert s[2..3]
with cost 2. Either way results in all characters being equal.
Intuition
The key observation is understanding when operations are necessary. If we scan through the string and find two adjacent characters that are different, we know that at least one operation must be performed to make them equal.
Consider what happens when we find s[i] ≠ s[i-1]
. We have a "boundary" between different characters. To eliminate this boundary, we have two choices:
- Make all characters to the left match the right side by inverting
s[0..i-1]
- Make all characters to the right match the left side by inverting
s[i..n-1]
The crucial insight is that we don't need to decide which final character ('0' or '1') the entire string should become. We just need to eliminate all boundaries between different characters. Once all boundaries are eliminated, all characters will naturally be the same.
Why can we process each boundary independently and greedily choose the minimum cost? Because operations at different boundaries don't interfere with each other in terms of creating or removing boundaries. When we invert a segment, we're essentially "propagating" one side's value across the boundary. The order of operations doesn't matter for the final cost.
For each boundary at position i
, the cost of fixing it is min(i, n-i)
. We choose whichever side is shorter to invert, minimizing the cost for that particular boundary. The total minimum cost is simply the sum of minimum costs for fixing all boundaries.
This greedy approach works because:
- Each boundary must be fixed exactly once
- The cost of fixing each boundary is independent
- We can always choose the cheaper option without affecting other boundaries' costs
Learn more about Greedy and Dynamic Programming patterns.
Solution Approach
The implementation follows a straightforward greedy algorithm that processes the string in a single pass:
-
Initialize variables: Set
ans = 0
to track the total minimum cost andn = len(s)
to store the string length. -
Iterate through the string: Loop through indices from
1
ton-1
, checking each pair of adjacent characters. -
Detect boundaries: For each position
i
, check ifs[i] != s[i-1]
. This condition identifies a boundary where adjacent characters differ. -
Calculate minimum cost: When a boundary is found at position
i
, we have two options:- Invert the left portion
s[0..i-1]
with costi
- Invert the right portion
s[i..n-1]
with costn - i
We choose the minimum:
min(i, n - i)
and add it to our running total. - Invert the left portion
-
Return the result: After processing all positions, return
ans
as the minimum total cost.
The algorithm's efficiency comes from its simplicity:
- Time Complexity:
O(n)
- we make a single pass through the string - Space Complexity:
O(1)
- we only use a constant amount of extra space
The code implementation:
class Solution:
def minimumCost(self, s: str) -> int:
ans, n = 0, len(s)
for i in range(1, n):
if s[i] != s[i - 1]:
ans += min(i, n - i)
return ans
This greedy approach guarantees the minimum cost because at each boundary, we independently choose the optimal (cheaper) operation without affecting the cost of handling other boundaries.
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 s = "00110"
:
Initial state: s = "00110"
, n = 5
, ans = 0
Step 1: Check position i = 1
- Compare
s[1] = '0'
withs[0] = '0'
- They're equal, no boundary here, continue
Step 2: Check position i = 2
- Compare
s[2] = '1'
withs[1] = '0'
- They're different! We found a boundary
- Cost options:
- Invert left
s[0..1]
: cost =i = 2
- Invert right
s[2..4]
: cost =n - i = 5 - 2 = 3
- Invert left
- Choose minimum:
min(2, 3) = 2
- Update:
ans = 0 + 2 = 2
Step 3: Check position i = 3
- Compare
s[3] = '1'
withs[2] = '1'
- They're equal, no boundary here, continue
Step 4: Check position i = 4
- Compare
s[4] = '0'
withs[3] = '1'
- They're different! Another boundary
- Cost options:
- Invert left
s[0..3]
: cost =i = 4
- Invert right
s[4..4]
: cost =n - i = 5 - 4 = 1
- Invert left
- Choose minimum:
min(4, 1) = 1
- Update:
ans = 2 + 1 = 3
Result: Return ans = 3
To verify: If we apply these operations:
- At position 2, invert left
s[0..1]
:"00110"
→"11110"
- At position 4, invert right
s[4..4]
:"11110"
→"11111"
All characters are now equal with total cost = 3, which matches our calculated minimum cost.
Solution Implementation
1class Solution:
2 def minimumCost(self, s: str) -> int:
3 """
4 Calculate the minimum cost to make all characters in the string identical.
5
6 When adjacent characters are different, we need to flip either the left part
7 or the right part of the string. The cost is the number of characters flipped.
8 We choose the side with fewer characters to minimize cost.
9
10 Args:
11 s: Input string containing characters to be made identical
12
13 Returns:
14 Minimum total cost to make all characters the same
15 """
16 total_cost = 0
17 string_length = len(s)
18
19 # Iterate through the string checking adjacent characters
20 for i in range(1, string_length):
21 # When adjacent characters differ, we need to perform a flip operation
22 if s[i] != s[i - 1]:
23 # Choose the cheaper option: flip left side (cost = i) or right side (cost = n - i)
24 cost_to_flip = min(i, string_length - i)
25 total_cost += cost_to_flip
26
27 return total_cost
28
1class Solution {
2 /**
3 * Calculates the minimum cost to make all characters in the string equal.
4 * The cost is determined by flipping prefixes or suffixes when adjacent characters differ.
5 *
6 * @param s the input string to process
7 * @return the minimum total cost to make all characters equal
8 */
9 public long minimumCost(String s) {
10 long totalCost = 0;
11 int stringLength = s.length();
12
13 // Iterate through adjacent character pairs
14 for (int i = 1; i < stringLength; ++i) {
15 // Check if current character differs from previous character
16 if (s.charAt(i) != s.charAt(i - 1)) {
17 // Add minimum cost between flipping prefix (0 to i-1) or suffix (i to end)
18 // Cost of flipping prefix: i operations
19 // Cost of flipping suffix: (stringLength - i) operations
20 totalCost += Math.min(i, stringLength - i);
21 }
22 }
23
24 return totalCost;
25 }
26}
27
1class Solution {
2public:
3 long long minimumCost(string s) {
4 long long totalCost = 0;
5 int stringLength = s.size();
6
7 // Iterate through the string comparing adjacent characters
8 for (int i = 1; i < stringLength; ++i) {
9 // When adjacent characters are different, we need to make them the same
10 if (s[i] != s[i - 1]) {
11 // Choose the minimum cost operation:
12 // - Cost of flipping prefix [0, i-1]: i operations
13 // - Cost of flipping suffix [i, n-1]: (n - i) operations
14 totalCost += min(i, stringLength - i);
15 }
16 }
17
18 return totalCost;
19 }
20};
21
1/**
2 * Calculates the minimum cost to make all characters in the string equal
3 * by flipping prefixes or suffixes.
4 *
5 * @param s - The input binary string containing only '0' and '1'
6 * @returns The minimum cost to make all characters equal
7 */
8function minimumCost(s: string): number {
9 // Initialize the total cost accumulator
10 let totalCost: number = 0;
11
12 // Get the length of the string
13 const stringLength: number = s.length;
14
15 // Iterate through the string starting from index 1
16 for (let currentIndex: number = 1; currentIndex < stringLength; currentIndex++) {
17 // Check if current character differs from previous character
18 if (s[currentIndex] !== s[currentIndex - 1]) {
19 // Add the minimum cost between:
20 // - Flipping prefix (cost = currentIndex)
21 // - Flipping suffix (cost = stringLength - currentIndex)
22 totalCost += Math.min(currentIndex, stringLength - currentIndex);
23 }
24 }
25
26 return totalCost;
27}
28
Time and Space Complexity
Time Complexity: O(n)
, where n
is the length of the string s
. The algorithm iterates through the string once with a single for loop from index 1 to n-1, performing constant time operations (comparison and minimum calculation) at each iteration.
Space Complexity: O(1)
. The algorithm only uses a fixed amount of extra space for variables ans
, n
, and the loop variable i
, regardless of the input size. No additional data structures that scale with input size are created.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Misunderstanding the Operation Effects
A common mistake is thinking that we need to track the actual state of the string after each operation. Developers might try to simulate the flips and maintain the modified string state:
# INCORRECT approach - unnecessarily complex
class Solution:
def minimumCost(self, s: str) -> int:
s = list(s) # Convert to list for mutation
total_cost = 0
for i in range(1, len(s)):
if s[i] != s[i - 1]:
if i <= len(s) - i:
# Flip left side
for j in range(i):
s[j] = '1' if s[j] == '0' else '0'
total_cost += i
else:
# Flip right side
for j in range(i, len(s)):
s[j] = '1' if s[j] == '0' else '0'
total_cost += len(s) - i
return total_cost
Why this is wrong: The operations don't need to be actually performed. The key insight is that whenever we encounter a boundary (different adjacent characters), we must perform an operation there. The actual final state doesn't matter - we only care about the cost.
2. Incorrect Cost Calculation
Another pitfall is miscalculating the operation costs, especially confusing 0-based indexing with the actual cost:
# INCORRECT - wrong cost calculation
class Solution:
def minimumCost(self, s: str) -> int:
ans, n = 0, len(s)
for i in range(1, n):
if s[i] != s[i - 1]:
# Wrong! Should be min(i, n - i), not min(i + 1, n - i + 1)
ans += min(i + 1, n - i + 1)
return ans
The correct understanding:
- Left-side inversion from index 0 to i-1 costs
i
(not i+1) - Right-side inversion from index i to n-1 costs
n - i
(not n-i+1)
3. Overthinking the Final State
Some might worry about whether the final string will be all '0's or all '1's and try to calculate both scenarios:
# INCORRECT - overthinking the problem
class Solution:
def minimumCost(self, s: str) -> int:
# Try to make all '0's
cost_all_zeros = self.calculateCost(s, '0')
# Try to make all '1's
cost_all_ones = self.calculateCost(s, '1')
return min(cost_all_zeros, cost_all_ones)
Why this is unnecessary: The greedy approach automatically finds the minimum cost regardless of whether the final string is all '0's or all '1's. The boundaries between different characters are what matter, not the target character.
Solution to Avoid These Pitfalls:
-
Focus on boundaries only: Remember that you only need to make decisions at positions where adjacent characters differ.
-
Use the simple formula: At each boundary position i, the cost is simply
min(i, n - i)
. -
Don't simulate operations: The actual string transformations don't need to be tracked - only the accumulated cost matters.
-
Trust the greedy approach: Each local optimal choice (choosing the cheaper flip at each boundary) leads to the global optimum.
The correct, simple implementation avoids all these pitfalls:
class Solution:
def minimumCost(self, s: str) -> int:
ans, n = 0, len(s)
for i in range(1, n):
if s[i] != s[i - 1]:
ans += min(i, n - i)
return ans
Which of the following is a good use case for backtracking?
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
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
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
Want a Structured Path to Master System Design Too? Don’t Miss This!