1578. Minimum Time to Make Rope Colorful
Problem Description
Alice has n
balloons tied in a row and each balloon is colored some color. This sequence of colors is given as a string colors
where colors[i]
is the color of the i
th balloon. Alice desires the string of balloons to be "colorful," which means no two adjacent balloons should share the same color. To help achieve this, Bob can remove balloons, but it takes time. The time it takes to remove each balloon is specified in an integer array neededTime
, where neededTime[i]
represents the seconds required to remove the i
th balloon.
The goal is to find the minimum amount of time Bob needs to spend to turn the string of balloons into one where no two consecutive balloons are of the same color.
Intuition
Given that we want to make the rope of balloons colorful with the least amount of time needed, we can intuit that when we encounter a sequence of balloons of the same color, we should remove all but one of them. Instead of randomly choosing which balloons to remove, we should aim to remove the balloons with the least time cost. The optimal balloon to keep is the one with the highest neededTime
value because removing it would contribute the most to the total time.
Throughout the string of balloons, we iteratively look for sequences of consecutive balloons with the same color. For each of these sequences, we calculate the total time needed to remove all balloons (s
) and the maximum time needed to remove a single balloon within that sequence (mx
). To make the sequence colorful, we will keep the balloon with the maximum neededTime
(the most costly to remove) and remove all others. Hence, we subtract the mx
from the total s
and add this to our answer (ans
).
The algorithm is efficient because it goes through the string once, using a while loop, checking each sequence of same-colored balloons and calculating the time cost on the fly. It has a linear time complexity relative to the length of the colors
string, making it suitable even for a large number of balloons.
Learn more about Greedy and Dynamic Programming patterns.
Solution Approach
The solution implementation follows a greedy approach, which entails iterating through the colors
string and grouping balloons with the same color. During this grouping, the algorithm also keeps track of the sum of neededTime
s of these balloons (s
) as well as the maximum neededTime
for a single balloon in the group (mx
). To do this, two pointers are used. The first pointer i
marks the beginning of a group of same-colored balloons, and a second pointer j
is used to find the end of this group.
The approach can be broken down into the following steps:
-
Initialize
ans
as zero, which will store the minimum total time required to make the rope colorful. -
Loop through the string
colors
using the indexi
, which serves as the starting point of each group of same-colored balloons:-
Within this loop, start a nested loop with index
j
, beginning at the same position asi
. -
Initialize
s
as zero, which will hold the sum of theneededTime
for all balloons currently in the sequence. -
Initialize
mx
as zero, which will keep track of the maximumneededTime
of a balloon in the current sequence. -
For each balloon that has the same color as the one at position
i
(i.e.,colors[j] == colors[i]
), accumulate itsneededTime
ins
and updatemx
if the current balloon'sneededTime[j]
is greater than the currentmx
. -
Increment
j
for as long as it does not exceed the length of thecolors
string and the color of thej
th balloon is the same as thei
th balloon. -
If the sequence length (
j - i
) is greater than 1, it means there are duplicate balloons. In this case, subtract the maximumneededTime
(mx
) from the sum ofneededTime
(s
) to get the time required to make the current sequence colorful and add this value toans
. -
Set
i
to the value ofj
to begin processing the next sequence.
-
-
Once the loop is complete, all the
neededTime
for removing necessary balloons to avoid consecutive same colors has been added toans
. Returnans
as the answer.
It is important to note that because this approach always chooses the most expensive balloon to keep in each consecutive sequence of same-colored balloons, the accumulated removal time is as small as possible. Essentially, you're selecting which balloons to remove based on the criteria of the lowest time cost, which aligns with our greedy strategy.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's consider an example where colors = "abcccbad"
and neededTime = [1, 2, 3, 4, 5, 6, 7, 8]
. Our goal is to make the sequence of balloons colorful by removing consecutive balloons of the same color in the least total time possible.
Using the solution approach outlined earlier, we can work through this step by step:
-
We initialize
ans
as zero. This will keep track of the minimum total time needed. -
We will loop through the string
colors
using indexi
. For simplicity, we consider each character as a group of same-colored balloons, even if it's a group of one.-
Start with
i = 0
; this is the first balloon with color 'a'. -
There are no consecutive balloons with the same color, so
i
is incremented to1
. -
Now at
i = 1
; this balloon has color 'b'. -
There are no consecutive balloons with the same color, so
i
is incremented to2
. -
At
i = 2
; this balloon has the color 'c'. -
We find there is a sequence of balloons with the same color 'c' starting from index
2
to4
. -
We set
j = 2
and start the nested loop. -
Initialize
s = 0
andmx = 0
. -
We go through the sequence, updating
s
andmx
as follows:- For
j = 2
,colors[j] = 'c'
, so we updates += neededTime[2]
(s becomes 3) andmx = max(mx, neededTime[2])
(mx becomes 3). - For
j = 3
,colors[j] = 'c'
,s += neededTime[3]
(s becomes 7) andmx = max(mx, neededTime[3])
(mx remains 3). - For
j = 4
,colors[j] = 'c'
,s += neededTime[4]
(s becomes 12) andmx = max(mx, neededTime[4])
(mx becomes 5).
- For
-
Since
j - i
(4 - 2) is greater than 1, we have duplicate balloons and we calculate the time to make the sequence colorful:ans += s - mx
(ans becomes 12 - 5 = 7). -
We set
i
to5
as the next starting point. -
Continuing with
i = 5
, there are no more consecutive balloons with the same color, so nothing gets added toans
.
-
-
Moving on, loop continues, but no more sequences of consecutive colors are found.
The final ans
represents the minimum total time to remove all the consecutive same-colored balloons and is equal to 7
in this example.
Solution Implementation
1from typing import List
2
3class Solution:
4 def minCost(self, colors: str, neededTime: List[int]) -> int:
5 # Initialize the answer and the iterator i.
6 total_time = 0
7 i = 0
8 n = len(colors)
9
10 # Process the string until we reach the end of colors.
11 while i < n:
12 # Start of the same color sequence.
13 start = i
14 # Initialize the sum and the maximum time for the current sequence.
15 sum_time = 0
16 max_time = 0
17
18 # Iterate over the sequence of same colors.
19 while i < n and colors[i] == colors[start]:
20 # Add the time for painting this balloon to the sum.
21 sum_time += neededTime[i]
22 # Update the max_time if this balloon's time is greater than the current max.
23 max_time = max(max_time, neededTime[i])
24 # Move to the next balloon.
25 i += 1
26
27 # If there was more than one balloon in the sequence,
28 # add the total time minus the time of the most expensive to repaint balloon.
29 if i - start > 1:
30 total_time += sum_time - max_time
31
32 # No need for updating i here, as it's already one past the current sequence.
33
34 # Return the total minimum time to repaint the balloons so that no adjacent balloons have the same color.
35 return total_time
36
37# Example usage:
38# sol = Solution()
39# colors = "abaac"
40# neededTime = [1,2,3,4,5]
41# print(sol.minCost(colors, neededTime)) # Should output 3
42
1class Solution {
2 public int minCost(String colors, int[] neededTime) {
3 // Initialize the total minimum cost to 0
4 int totalMinCost = 0;
5 // Obtain the total number of balloons
6 int balloonsCount = neededTime.length;
7
8 // Iterate through the array of needed time to check for consecutive balloons of the same color
9 for (int startIndex = 0, endIndex = 0; startIndex < balloonsCount; startIndex = endIndex) {
10 // Move the endIndex to the start of the current sequence
11 endIndex = startIndex;
12 // Initialize the sum of needed time for all balloons in the current sequence and the maximum needed time
13 int sumNeededTime = 0, maxNeededTime = 0;
14 // Process consecutive balloons of the same color
15 while (endIndex < balloonsCount && colors.charAt(endIndex) == colors.charAt(startIndex)) {
16 // Add the needed time of the current balloon to the sum
17 sumNeededTime += neededTime[endIndex];
18 // Update the maximum needed time for the current sequence
19 maxNeededTime = Math.max(maxNeededTime, neededTime[endIndex]);
20 // Move to the next balloon
21 ++endIndex;
22 }
23 // If there is more than one balloon of the same color consecutively,
24 // increment the total minimum cost by the sum of needed times minus the maximum needed time
25 if (endIndex - startIndex > 1) {
26 totalMinCost += sumNeededTime - maxNeededTime;
27 }
28 }
29 // Return the total minimum cost to make all balloons have distinct colors
30 return totalMinCost;
31 }
32}
33
1class Solution {
2public:
3 int minCost(string colors, vector<int>& neededTime) {
4 // This variable will hold the minimum total time
5 int totalTime = 0;
6 // The size of the colors string
7 int n = colors.size();
8
9 // Loop through the colors string
10 for (int i = 0, j = 0; i < n; i = j) {
11 // Look for a sequence of the same color
12 j = i;
13 // 'sumTimes' holds the sum of time for balloons of the same color group
14 int sumTimes = 0;
15 // 'maxTime' holds the maximum time needed for a single balloon in the same color group
16 int maxTime = 0;
17
18 // While we haven't reached the end of the string and the current color is
19 // the same as the one we started this sequence with, add to sumTimes and
20 // update maxTime if necessary
21 while (j < n && colors[j] == colors[i]) {
22 sumTimes += neededTime[j];
23 maxTime = max(maxTime, neededTime[j]);
24 ++j;
25 }
26
27 // If we have more than one balloon with the same color in sequence,
28 // add to the total time the sum of needed time minus the maximum time
29 // needed for a single balloon (we are keeping the one which takes longest to remove)
30 if (j - i > 1) {
31 totalTime += sumTimes - maxTime;
32 }
33 }
34
35 // Return the total time calculated
36 return totalTime;
37 }
38};
39
1function minCost(colors: string, neededTime: number[]): number {
2 let totalTime = 0; // This will hold the minimum total time
3 const n = colors.length; // The size of the colors string
4
5 // Loop through the colors string
6 for (let i = 0, j = 0; i < n; i = j) {
7 // Start of the same color sequence
8 j = i;
9 // Hold the sum of times for balloons of the same color group
10 let sumTimes = 0;
11 // Hold the maximum time needed for a single balloon in the same color group
12 let maxTime = 0;
13
14 // While we haven't reached the end of the string and the current color is the same
15 // as the one we started this sequence with, add to sumTimes and update maxTime
16 while (j < n && colors[j] === colors[i]) {
17 sumTimes += neededTime[j];
18 maxTime = Math.max(maxTime, neededTime[j]);
19 j++;
20 }
21
22 // Add to total time the sum of needed times minus the max needed time for this sequence
23 // This is because we're removing all but one balloon in the sequence
24 if (j - i > 1) {
25 totalTime += sumTimes - maxTime;
26 }
27 }
28
29 // Return the total minimum time calculated
30 return totalTime;
31}
32
Time and Space Complexity
Time Complexity
The provided code consists of a single while loop that iterates through the string colors
. Inside the loop, there's another while loop that also iterates through the string but only when it finds consecutive characters that are the same. Despite this nested structure, the inner loop does not lead to a quadratic time complexity, because each character in the string colors
is visited exactly once. After the inner loop, the index i
jumps to the position j
, skipping all the characters that have already been accounted for.
Thus, each character in the string causes an iteration of the outer loop, and the inner loop only runs for characters of a sequence of identical colors once. This leads to a linear time complexity with respect to the length of the string colors
.
Hence, the time complexity is O(n)
, where n
is the length of colors
.
Space Complexity
The code uses a constant amount of extra space: variables ans
, i
, s
, mx
, and j
. When iterating over the input, no additional space that scales with the size of the input is used. The input itself (colors
and neededTime
) is not counted towards the space complexity.
Therefore, the space complexity is O(1)
since it does not allocate any additional space that grows with the input size.
Learn more about how to find time and space complexity quickly using problem constraints.
What's the output of running the following function using input [30, 20, 10, 100, 33, 12]
?
1def fun(arr: List[int]) -> List[int]:
2 import heapq
3 heapq.heapify(arr)
4 res = []
5 for i in range(3):
6 res.append(heapq.heappop(arr))
7 return res
8
1public static int[] fun(int[] arr) {
2 int[] res = new int[3];
3 PriorityQueue<Integer> heap = new PriorityQueue<>();
4 for (int i = 0; i < arr.length; i++) {
5 heap.add(arr[i]);
6 }
7 for (int i = 0; i < 3; i++) {
8 res[i] = heap.poll();
9 }
10 return res;
11}
12
1class HeapItem {
2 constructor(item, priority = item) {
3 this.item = item;
4 this.priority = priority;
5 }
6}
7
8class MinHeap {
9 constructor() {
10 this.heap = [];
11 }
12
13 push(node) {
14 // insert the new node at the end of the heap array
15 this.heap.push(node);
16 // find the correct position for the new node
17 this.bubble_up();
18 }
19
20 bubble_up() {
21 let index = this.heap.length - 1;
22
23 while (index > 0) {
24 const element = this.heap[index];
25 const parentIndex = Math.floor((index - 1) / 2);
26 const parent = this.heap[parentIndex];
27
28 if (parent.priority <= element.priority) break;
29 // if the parent is bigger than the child then swap the parent and child
30 this.heap[index] = parent;
31 this.heap[parentIndex] = element;
32 index = parentIndex;
33 }
34 }
35
36 pop() {
37 const min = this.heap[0];
38 this.heap[0] = this.heap[this.size() - 1];
39 this.heap.pop();
40 this.bubble_down();
41 return min;
42 }
43
44 bubble_down() {
45 let index = 0;
46 let min = index;
47 const n = this.heap.length;
48
49 while (index < n) {
50 const left = 2 * index + 1;
51 const right = left + 1;
52
53 if (left < n && this.heap[left].priority < this.heap[min].priority) {
54 min = left;
55 }
56 if (right < n && this.heap[right].priority < this.heap[min].priority) {
57 min = right;
58 }
59 if (min === index) break;
60 [this.heap[min], this.heap[index]] = [this.heap[index], this.heap[min]];
61 index = min;
62 }
63 }
64
65 peek() {
66 return this.heap[0];
67 }
68
69 size() {
70 return this.heap.length;
71 }
72}
73
74function fun(arr) {
75 const heap = new MinHeap();
76 for (const x of arr) {
77 heap.push(new HeapItem(x));
78 }
79 const res = [];
80 for (let i = 0; i < 3; i++) {
81 res.push(heap.pop().item);
82 }
83 return res;
84}
85
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
LeetCode 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 we
Want a Structured Path to Master System Design Too? Donāt Miss This!