Before attempting this problem, you should be comfortable with:
Two Pointers Technique - Using multiple indices to traverse arrays and identify groups of consecutive elements
Greedy Algorithms - Making locally optimal choices (keeping the most expensive balloon) to achieve a globally optimal solution
String Traversal - Iterating through characters while comparing adjacent elements
1. Two Pointers - I
Intuition
The goal is to remove consecutive balloons of the same color such that no two adjacent balloons share a color. When we find a group of consecutive same-colored balloons, we need to keep exactly one and remove the rest. To minimize the total removal time, we should keep the balloon with the highest removal cost and remove all others.
Algorithm
Initialize res = 0 and i = 0.
While i < n:
Find all consecutive balloons with the same color starting at i.
Track the sum of removal times (curr) and the maximum removal time (maxi) in this group.
Add curr - maxi to res (we keep the most expensive one, remove the rest).
classSolution:defminCost(self, colors:str, neededTime: List[int])->int:
n =len(neededTime)
res = i =0while i < n:
j = i
maxi = curr =0while j < n and colors[j]== colors[i]:
maxi =max(maxi, neededTime[j])
curr += neededTime[j]
j +=1
res += curr - maxi
i = j
return res
publicclassSolution{publicintminCost(String colors,int[] neededTime){int n = neededTime.length;int res =0, i =0;while(i < n){int j = i, maxi =0, curr =0;while(j < n && colors.charAt(j)== colors.charAt(i)){
maxi =Math.max(maxi, neededTime[j]);
curr += neededTime[j];
j++;}
res += curr - maxi;
i = j;}return res;}}
classSolution{public:intminCost(string colors, vector<int>& neededTime){int n = neededTime.size();int res =0, i =0;while(i < n){int j = i, maxi =0, curr =0;while(j < n && colors[j]== colors[i]){
maxi =max(maxi, neededTime[j]);
curr += neededTime[j];
j++;}
res += curr - maxi;
i = j;}return res;}};
classSolution{/**
* @param {string} colors
* @param {number[]} neededTime
* @return {number}
*/minCost(colors, neededTime){const n = neededTime.length;let res =0,
i =0;while(i < n){let j = i,
maxi =0,
curr =0;while(j < n && colors[j]=== colors[i]){
maxi = Math.max(maxi, neededTime[j]);
curr += neededTime[j];
j++;}
res += curr - maxi;
i = j;}return res;}}
publicclassSolution{publicintMinCost(string colors,int[] neededTime){int n = neededTime.Length;int res =0, i =0;while(i < n){int j = i, maxi =0, curr =0;while(j < n && colors[j]== colors[i]){
maxi = Math.Max(maxi, neededTime[j]);
curr += neededTime[j];
j++;}
res += curr - maxi;
i = j;}return res;}}
funcminCost(colors string, neededTime []int)int{
n :=len(neededTime)
res, i :=0,0for i < n {
j := i
maxi, curr :=0,0for j < n && colors[j]== colors[i]{if neededTime[j]> maxi {
maxi = neededTime[j]}
curr += neededTime[j]
j++}
res += curr - maxi
i = j
}return res
}
class Solution {funminCost(colors: String, neededTime: IntArray): Int {val n = neededTime.size
var res =0var i =0while(i < n){var j = i
var maxi =0var curr =0while(j < n && colors[j]== colors[i]){
maxi =maxOf(maxi, neededTime[j])
curr += neededTime[j]
j++}
res += curr - maxi
i = j
}return res
}}
classSolution{funcminCost(_ colors:String,_ neededTime:[Int])->Int{let n = neededTime.count
let chars =Array(colors)var res =0var i =0while i < n {var j = i
var maxi =0var curr =0while j < n && chars[j]== chars[i]{
maxi =max(maxi, neededTime[j])
curr += neededTime[j]
j +=1}
res += curr - maxi
i = j
}return res
}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(1) extra space.
2. Two Pointers - II
Intuition
Instead of finding entire groups at once, we can process pairs of adjacent balloons. When two adjacent balloons have the same color, we remove the one with smaller removal time. We keep a pointer l to track the balloon we're keeping, and compare it with each new balloon at position r.
Algorithm
Initialize l = 0 and res = 0.
For each r from 1 to n-1:
If colors[l] == colors[r] (same color):
If neededTime[l] < neededTime[r]: add neededTime[l] to res and update l = r.
Otherwise: add neededTime[r] to res (keep l unchanged).
classSolution:defminCost(self, colors:str, neededTime: List[int])->int:
l, res =0,0for r inrange(1,len(colors)):if colors[l]== colors[r]:if neededTime[l]< neededTime[r]:
res += neededTime[l]
l = r
else:
res += neededTime[r]else:
l = r
return res
publicclassSolution{publicintminCost(String colors,int[] neededTime){int l =0, res =0;for(int r =1; r < colors.length(); r++){if(colors.charAt(l)== colors.charAt(r)){if(neededTime[l]< neededTime[r]){
res += neededTime[l];
l = r;}else{
res += neededTime[r];}}else{
l = r;}}return res;}}
classSolution{public:intminCost(string colors, vector<int>& neededTime){int l =0, res =0;for(int r =1; r < colors.size(); r++){if(colors[l]== colors[r]){if(neededTime[l]< neededTime[r]){
res += neededTime[l];
l = r;}else{
res += neededTime[r];}}else{
l = r;}}return res;}};
classSolution{/**
* @param {string} colors
* @param {number[]} neededTime
* @return {number}
*/minCost(colors, neededTime){let l =0,
res =0;for(let r =1; r < colors.length; r++){if(colors[l]=== colors[r]){if(neededTime[l]< neededTime[r]){
res += neededTime[l];
l = r;}else{
res += neededTime[r];}}else{
l = r;}}return res;}}
publicclassSolution{publicintMinCost(string colors,int[] neededTime){int l =0, res =0;for(int r =1; r < colors.Length; r++){if(colors[l]== colors[r]){if(neededTime[l]< neededTime[r]){
res += neededTime[l];
l = r;}else{
res += neededTime[r];}}else{
l = r;}}return res;}}
funcminCost(colors string, neededTime []int)int{
l, res :=0,0for r :=1; r <len(colors); r++{if colors[l]== colors[r]{if neededTime[l]< neededTime[r]{
res += neededTime[l]
l = r
}else{
res += neededTime[r]}}else{
l = r
}}return res
}
class Solution {funminCost(colors: String, neededTime: IntArray): Int {var l =0var res =0for(r in1 until colors.length){if(colors[l]== colors[r]){if(neededTime[l]< neededTime[r]){
res += neededTime[l]
l = r
}else{
res += neededTime[r]}}else{
l = r
}}return res
}}
classSolution{funcminCost(_ colors:String,_ neededTime:[Int])->Int{let chars =Array(colors)var l =0var res =0for r in1..<chars.count {if chars[l]== chars[r]{if neededTime[l]< neededTime[r]{
res += neededTime[l]
l = r
}else{
res += neededTime[r]}}else{
l = r
}}return res
}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(1) extra space.
3. Two Pointers - III
Intuition
We can simplify the logic further by tracking the maximum removal time seen so far in the current group of same-colored balloons. For each balloon, we add the minimum of the current maximum and the current balloon's time to the result, then update the maximum. When we encounter a different color, we reset the maximum to 0.
Algorithm
Initialize res = 0 and maxi = 0.
For each index i:
If i > 0 and colors[i] != colors[i-1], reset maxi = 0.
classSolution:defminCost(self, colors:str, neededTime: List[int])->int:
res = maxi =0for i inrange(len(colors)):if i and colors[i]!= colors[i -1]:
maxi =0
res +=min(maxi, neededTime[i])
maxi =max(maxi, neededTime[i])return res
publicclassSolution{publicintminCost(String colors,int[] neededTime){int res =0, maxi =0;for(int i =0; i < colors.length(); i++){if(i >0&& colors.charAt(i)!= colors.charAt(i -1)){
maxi =0;}
res +=Math.min(maxi, neededTime[i]);
maxi =Math.max(maxi, neededTime[i]);}return res;}}
classSolution{public:intminCost(string colors, vector<int>& neededTime){int res =0, maxi =0;for(int i =0; i < colors.size(); i++){if(i >0&& colors[i]!= colors[i -1]){
maxi =0;}
res +=min(maxi, neededTime[i]);
maxi =max(maxi, neededTime[i]);}return res;}};
classSolution{/**
* @param {string} colors
* @param {number[]} neededTime
* @return {number}
*/minCost(colors, neededTime){let res =0,
maxi =0;for(let i =0; i < colors.length; i++){if(i >0&& colors[i]!== colors[i -1]){
maxi =0;}
res += Math.min(maxi, neededTime[i]);
maxi = Math.max(maxi, neededTime[i]);}return res;}}
publicclassSolution{publicintMinCost(string colors,int[] neededTime){int res =0, maxi =0;for(int i =0; i < colors.Length; i++){if(i >0&& colors[i]!= colors[i -1]){
maxi =0;}
res += Math.Min(maxi, neededTime[i]);
maxi = Math.Max(maxi, neededTime[i]);}return res;}}
funcminCost(colors string, neededTime []int)int{
res, maxi :=0,0for i :=0; i <len(colors); i++{if i >0&& colors[i]!= colors[i-1]{
maxi =0}if maxi < neededTime[i]{
res += maxi
maxi = neededTime[i]}else{
res += neededTime[i]}}return res
}
class Solution {funminCost(colors: String, neededTime: IntArray): Int {var res =0var maxi =0for(i in colors.indices){if(i >0&& colors[i]!= colors[i -1]){
maxi =0}
res +=minOf(maxi, neededTime[i])
maxi =maxOf(maxi, neededTime[i])}return res
}}
classSolution{funcminCost(_ colors:String,_ neededTime:[Int])->Int{let chars =Array(colors)var res =0var maxi =0for i in0..<chars.count {if i >0&& chars[i]!= chars[i -1]{
maxi =0}
res +=min(maxi, neededTime[i])
maxi =max(maxi, neededTime[i])}return res
}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(1) extra space.
Common Pitfalls
Removing the Wrong Balloon
When encountering consecutive same-colored balloons, some may instinctively remove the first balloon encountered or remove all of them. The correct strategy is to keep the balloon with the maximum removal time and remove all others, minimizing total cost.
Forgetting to Reset State Between Groups
When processing groups of consecutive same-colored balloons, failing to reset the maximum or sum tracking variables when transitioning to a new color group leads to incorrect calculations. Each group must be processed independently.
Off-by-One Errors in Group Boundaries
When using two pointers to identify groups of consecutive balloons, it is easy to mishandle the boundary conditions, either including an extra balloon from the next group or missing the last balloon in the current group. Carefully ensure the inner loop condition correctly identifies when colors change.