3784. Minimum Deletion Cost to Make All Characters Equal
Problem Description
You are given a string s of length n and an integer array cost of the same length, where cost[i] is the cost to delete the ith character of s.
You are allowed to delete any number of characters from s (this includes deleting none at all). After the deletions, the remaining string must satisfy two conditions:
- It must be non-empty (at least one character must remain).
- It must consist of equal characters (every remaining character must be the same).
Your task is to find a way to perform these deletions so that the total deletion cost (the sum of cost[i] for every deleted character i) is as small as possible.
In other words, you must choose a single character to keep, then delete every character in s that is different from your chosen character, paying the corresponding cost for each deletion. You want to pick the choice that minimizes the overall cost.
Return an integer denoting the minimum total deletion cost required.
How We Pick the Algorithm
Why Hash Table / Counting?
This problem maps to Hash Table / Counting through a short path in the full flowchart.
Computing the total deletion cost and per-character cost sums in a hash table lets us pick the minimum-cost character to keep.
Open in FlowchartIntuition
The key observation is that the final string must consist of only one type of character. This means that whatever character we decide to keep, we have to delete all other characters that are different from it.
So the real decision is simple: which character do we keep? Once we fix the character to keep, the cost is completely determined—it is the sum of the deletion costs of every other character.
Suppose the total cost of deleting every character in the string is tot. If we decide to keep character c, then we don't delete any occurrence of c, so we save the combined cost of all c characters. Let g[c] be the total deletion cost of all occurrences of character c. Then the cost of keeping c is exactly tot - g[c].
To minimize the total cost, we want to save as much as possible, which means we should keep the character whose group has the largest total cost. Equivalently, we compute tot - g[c] for every distinct character c and take the minimum.
This naturally leads to the approach: first accumulate the total cost tot and the per-character cost sums in a hash table g, then enumerate each character and pick the smallest value of tot - g[c].
Solution Approach
Solution 1: Grouping + Enumeration
We first calculate the total deletion cost for each character in the string and store it in a hash table g, where the key is the character and the value is the corresponding total deletion cost. At the same time, we calculate the total cost tot of deleting all characters.
We iterate through the string s together with the array cost using zip. For each pair of character c and cost v:
- We add
vtotot, accumulating the total deletion cost of the whole string. - We add
vtog[c], accumulating the total deletion cost for that specific character.
Next, we iterate through the hash table g. For each character c, the minimum deletion cost required to keep that character is tot - g[c] (delete everything except the occurrences of c). The final answer is the minimum value of tot - x over all the per-character cost sums x in g.values().
The time complexity is O(n), where n is the length of the string s, since we make a single pass to build the hash table and another pass over the distinct characters. The space complexity is O(|\Sigma|), where |\Sigma| is the size of the character set (at most the number of distinct characters in s).
Example Walkthrough
Let's trace through the solution approach with a small example.
Input:
s = "abaac"cost = [1, 2, 3, 4, 5]
Goal: Keep one character type and delete all others at minimum total cost.
Step 1: Build the per-character cost sums g and the total cost tot.
We iterate through s and cost together using zip. For each pair (c, v), we add v to tot and add v to g[c].
| Index | Char c | Cost v | Running tot | g after update |
|---|---|---|---|---|
| 0 | a | 1 | 1 | {a: 1} |
| 1 | b | 2 | 3 | {a: 1, b: 2} |
| 2 | a | 3 | 6 | {a: 4, b: 2} |
| 3 | a | 4 | 10 | {a: 8, b: 2} |
| 4 | c | 5 | 15 | {a: 8, b: 2, c: 5} |
After this pass:
tot = 15(the cost of deleting every character).g = {a: 8, b: 2, c: 5}.
Step 2: Enumerate each character and compute tot - g[c].
If we decide to keep character c, we save the cost of all its occurrences (g[c]), so the deletion cost is tot - g[c]:
Keep c | g[c] | tot - g[c] | Meaning |
|---|---|---|---|
a | 8 | 15 - 8 = 7 | Delete the b and c (cost 2 + 5) |
b | 2 | 15 - 2 = 13 | Delete all as and c (cost 8 + 5) |
c | 5 | 15 - 5 = 10 | Delete all as and b (cost 8 + 2) |
Step 3: Take the minimum.
The smallest value is 7, achieved by keeping the character a (whose group has the largest total cost 8, meaning we save the most).
Output: 7
This confirms the intuition: to minimize the deletion cost, we keep the character whose occurrences have the largest combined cost, since keeping it lets us avoid paying that cost.
Solution Implementation
1from collections import defaultdict
2from typing import List
3
4
5class Solution:
6 def minCost(self, s: str, cost: List[int]) -> int:
7 # total_cost: sum of costs for every character in the string
8 total_cost = 0
9 # char_cost_sum: maps each distinct character to the sum of its costs
10 char_cost_sum = defaultdict(int)
11
12 # Accumulate the overall total and the per-character totals in one pass
13 for char, char_cost in zip(s, cost):
14 total_cost += char_cost
15 char_cost_sum[char] += char_cost
16
17 # Subtracting the largest per-character group from the total
18 # leaves the minimum cost (we keep the most expensive group intact).
19 return min(total_cost - group_cost for group_cost in char_cost_sum.values())
20```
21
22**Equivalent, slightly clearer alternative** (same result, expressing intent directly):
23
24```python3
25from collections import defaultdict
26from typing import List
27
28
29class Solution:
30 def minCost(self, s: str, cost: List[int]) -> int:
31 total_cost = 0
32 char_cost_sum = defaultdict(int)
33
34 for char, char_cost in zip(s, cost):
35 total_cost += char_cost
36 char_cost_sum[char] += char_cost
37
38 # min(total - x) == total - max(x); keep the heaviest character group
39 return total_cost - max(char_cost_sum.values())
401class Solution {
2 /**
3 * Computes the minimum cost based on grouping character costs.
4 *
5 * Strategy:
6 * 1. Accumulate the total cost of all characters.
7 * 2. Group the costs by their corresponding character, summing each group.
8 * 3. The answer is the total cost minus the largest single group's sum,
9 * which is equivalent to taking the minimum of (total - groupSum)
10 * across all groups.
11 *
12 * @param s the input string of characters
13 * @param cost the cost associated with each character at the same index
14 * @return the minimum cost after removing the most "valuable" group
15 */
16 public long minCost(String s, int[] cost) {
17 // Total cost of every character in the string.
18 long totalCost = 0;
19
20 // Maps each distinct character to the summed cost of its occurrences.
21 Map<Character, Long> costByChar = new HashMap<>(26);
22
23 // Build the total cost and per-character grouped costs in one pass.
24 for (int i = 0; i < cost.length; ++i) {
25 totalCost += cost[i];
26 costByChar.merge(s.charAt(i), (long) cost[i], Long::sum);
27 }
28
29 // Initialize the answer with the full total as the upper bound.
30 long minResult = totalCost;
31
32 // For each character group, consider removing that group's entire cost
33 // and keep track of the smallest remaining total.
34 for (long groupCost : costByChar.values()) {
35 minResult = Math.min(minResult, totalCost - groupCost);
36 }
37
38 return minResult;
39 }
40}
411class Solution {
2public:
3 long long minCost(string s, vector<int>& cost) {
4 // Accumulate the total cost of all characters.
5 long long totalCost = 0;
6
7 // Map each distinct character to the sum of its costs.
8 unordered_map<char, long long> costByChar;
9
10 // Build the total cost and the per-character cost sums.
11 for (int i = 0; i < static_cast<int>(cost.size()); ++i) {
12 totalCost += cost[i];
13 costByChar[s[i]] += cost[i];
14 }
15
16 // Start with the assumption of removing everything (upper bound).
17 long long answer = totalCost;
18
19 // For each character group, consider keeping that group entirely
20 // and removing the rest, then take the minimum remaining cost.
21 for (const auto& [character, groupCost] : costByChar) {
22 answer = min(answer, totalCost - groupCost);
23 }
24
25 return answer;
26 }
27};
28```
29
30**A note on correctness:** This approach keeps only the *single* character group with the maximum total cost. This is correct **only** when the string consists of characters that are all the same (or when the intended problem is "keep one group").
31
32For the classic LeetCode problem **1578 (Minimum Cost to Make at Least One String Repeating / removing characters to avoid equal adjacent letters)**, the standard solution differs — it processes consecutive equal-character runs and keeps the most expensive character in each run:
33
34```cpp
35class Solution {
36public:
37 long long minCost(string s, vector<int>& cost) {
38 long long totalRemoved = 0; // Accumulated cost of removed characters.
39 int i = 0;
40 int n = static_cast<int>(s.size());
41
42 while (i < n) {
43 char currentChar = s[i];
44 long long groupSum = 0; // Sum of costs in the current run.
45 int groupMax = 0; // Maximum single cost in the current run.
46
47 // Expand over the run of identical adjacent characters.
48 while (i < n && s[i] == currentChar) {
49 groupSum += cost[i];
50 groupMax = max(groupMax, cost[i]);
51 ++i;
52 }
53
54 // Keep the most expensive char, remove the rest of the run.
55 totalRemoved += groupSum - groupMax;
56 }
57
58 return totalRemoved;
59 }
60};
611/**
2 * Minimum Time to Make Rope Colorful.
3 * Removes balloons so that no two adjacent balloons share the same color,
4 * minimizing the total removal time.
5 *
6 * @param s The colors of the balloons, one character per balloon.
7 * @param cost The time needed to remove each corresponding balloon.
8 * @returns The minimum total time to remove balloons.
9 */
10function minCost(s: string, cost: number[]): number {
11 const n: number = s.length;
12 let answer: number = 0;
13 let index: number = 0;
14
15 // Scan through the string, processing each run of identical adjacent colors.
16 while (index < n) {
17 const color: string = s[index];
18 let groupSum: number = 0; // Total cost of all balloons in the current run.
19 let groupMax: number = 0; // Largest single cost in the current run.
20
21 // Extend the run while the color stays the same.
22 while (index < n && s[index] === color) {
23 groupSum += cost[index];
24 groupMax = Math.max(groupMax, cost[index]);
25 index++;
26 }
27
28 // Keep the most expensive balloon in the run; remove the rest.
29 answer += groupSum - groupMax;
30 }
31
32 return answer;
33}
34Time and Space Complexity
-
Time Complexity:
O(n), wherenis the length of the strings. The first loop iterates over the stringsand thecostarray simultaneously usingzip, performing constant-time operations (accumulatingtotand updating the dictionaryg) for each of thencharacters. The finalmincomputation iterates over the values ing, which contains at most|Σ|entries, and since|Σ| ≤ n, this step is bounded byO(n). Therefore, the overall time complexity isO(n). -
Space Complexity:
O(|Σ|), whereΣis the set of distinct characters in the strings. The dictionarygstores one entry per distinct character, so its size is at most|Σ|. The variablestotand the generator expression in themincall use only constant extra space. Thus, the overall space complexity isO(|Σ|).
Pattern Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Misreading the problem as "delete adjacent duplicates" (LeetCode 1578)
The most common mistake is confusing this problem with the well-known "Minimum Deletion Cost to Avoid Repeating Letters" (LeetCode 1578), where you must delete characters so that no two adjacent characters are the same, keeping the most expensive one in each consecutive run.
In this problem, the requirement is the opposite: the final string must consist of equal characters, so you pick one character value to keep across the entire string and delete every other character.
- Wrong intuition (1578-style): group by consecutive runs and keep the max in each run.
- Correct here: group by character value globally and keep the single heaviest group.
Solution: Read carefully whether the grouping is by adjacency or by character identity. Here it is character identity, so accumulate costs into a hash table keyed by the character, not by run position.
Pitfall 2: Forgetting the "non-empty" constraint and returning 0
One might think "delete nothing" or "the minimum cost is always 0." Because the result string must be non-empty and all equal, you cannot keep two different characters. You must keep exactly one character group, which forces you to delete every other group.
Solution: The answer is total_cost - max(group_cost), which automatically keeps the heaviest group (at least one character) and is never trivially 0 unless the string already has a single distinct character with all-zero costs for the rest.
Pitfall 3: Calling max() / min() on an empty sequence
If s is empty, char_cost_sum is empty and max(char_cost_sum.values()) raises a ValueError. While the constraints usually guarantee n >= 1, defensive code should guard against it.
Solution: Validate input length or provide a default:
if not s:
return 0
return total_cost - max(char_cost_sum.values())
Or use the default argument of max:
return total_cost - max(char_cost_sum.values(), default=0)
Pitfall 4: Mismatched lengths between s and cost
Using zip(s, cost) silently stops at the shorter sequence. If cost is shorter than s (or vice versa) due to a bug upstream, some characters are silently ignored, producing a wrong total without an error.
Solution: Assert the lengths match before processing:
assert len(s) == len(cost), "s and cost must have the same length"
Pitfall 5: Integer overflow concerns / unnecessary sorting
Some attempt to sort the groups to find the maximum, adding an unnecessary O(k log k) step, or worry about overflow.
Solution: A single max() over the group sums is O(k) and sufficient. In Python, integers are arbitrary precision, so overflow is a non-issue; in other languages, use a 64-bit type since the summed cost can exceed 32-bit limits.
Ready to land your dream job?
Unlock your dream job with a 5-minute quiz for a personalized study roadmap!
Get My RoadmapWhich algorithm is best for finding the shortest distance between two points in an unweighted graph?
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 If you prefer videos here's a video that explains recursion in a fun and easy way 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 him
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 describes how the time needed
Want a Structured Path to Master System Design Too? Don’t Miss This!