2136. Earliest Possible Day of Full Bloom


Problem Description

In this problem, you have n flower seeds that you need to plant and grow. Each flower seed has two associated times:

  • plantTime[i]: This is the number of full days it takes to plant the i-th seed. You can only work on planting one seed per day, but you don't have to do it on consecutive days. However, you cannot consider a seed as planted until you've spent the total number of plantTime[i] days on it.

  • growTime[i]: This is the number of full days it takes for the i-th seed to grow and bloom after it has been completely planted. Once a flower blooms, at the end of its growth time, it stays bloomed forever.

The task is to arrange your planting strategy to ensure that all the seeds are blooming by the earliest possible day. You can plant the seeds in any order starting from day 0.

Intuition

To solve the problem, the main insight is that seeds with a longer growth time ("growTime") should be prioritised for planting. This is because the growth process can occur simultaneously with the planting of other seeds. In other words, if a seed takes a long time to grow, you want to start that growth period as soon as possible, so it doesn't delay the point in time when all flowers are blooming.

Here is the step-by-step intuition for the solution:

  1. Pair each seed's planting time with its growth time and consider these as a combined attribute of the seed.

  2. Sort the seeds in descending order of their growth time. By doing this, we prioritize planting seeds with the longest growth time.

  3. Initialize two variables, t and ans. t will keep track of the total time spent planting seeds up to the current seed, and ans will represent the earliest day all seeds are blooming. Start with both at zero.

  4. Iterate through the sorted list of pairs:

    • Increment t by the current seed's planting time. This represents the day by which planting of this seed is finished.

    • Calculate the potential blooming day for the current seed by adding its growth time to t.

    • Update ans to be the maximum of its current value and the potential blooming day for the current seed. This ensures that ans always represents the day by which all seeds seen so far will have bloomed.

  5. After the iteration, ans will contain the earliest possible day where all seeds are blooming by following the optimal planting order.

The logic behind this approach is that by focusing on getting the long-growth seeds growing as early as possible, you minimize the waiting time at the end of the planting schedule for the flowers to bloom.

Learn more about Greedy and Sorting patterns.

Not Sure What to Study? Take the 2-min Quiz to Find Your Missing Piece:

How would you design a stack which has a function min that returns the minimum element in the stack, in addition to push and pop? All push, pop, min should have running time O(1).

Solution Approach

To implement the solution, we utilize sorting and straightforward iteration to find the optimal sequence for planting the seeds.

  • Sorting: We begin by pairing each plantTime with the corresponding growTime for each seed. Then, we sort the pairs based on growTime in descending order. This sorting places the seeds with the longest growth period first, which are the seeds we want to plant as soon as possible.

    1# Pseudocode for [Sorting](/problems/sorting_summary)
    2paired_times = zip(plantTime, growTime)
    3sorted_times = sorted(paired_times, key=lambda x: -x[1])
  • Iteration: Next, we iterate through our sorted pairs and simulate the planting and growing process.

    1# Pseudocode for Iteration
    2t = 0 # Total days spent planting
    3ans = 0 # The earliest day all seeds are blooming
    4for pt, gt in sorted_times:
    5    t += pt # Increment total planting days by current seed's planting time
    6    ans = max(ans, t + gt) # Update ans to the max of current ans and the potential bloom day
    • During each step, we adjust our t value by adding the current seed's planting time. This t represents the time passed solely due to planting activities.

    • We then consider the total time until the current seed blooms, which is t + gt (gt - growth time for the current seed). Since growth occurs after planting, it makes sense to sum these up.

    • The ans value is updated to ensure it represents the day when each seed planted so far will have bloomed. By using the max function, we ensure that it reflects the latest bloom amongst all seeds considered.

  • Variables: Two key variables are used:

    • t: Tracks the cumulative days spent on planting.
    • ans: Tracks the earliest possible day where all seeds are blooming.
  • Return Value: After the loop completes, ans represents the earliest day by which all the seeds would have bloomed. We arrived at this number by iteratively keeping track of the maximal end date for the blooming of each seed, hence considering the fact that while one seed is growing, we could be planting another.

The code demonstrates proper utilization of Python's list operations, such as zipping, sorting, and iteration. Moreover, the use of a simple max function elegantly resolves the problem of comparing bloom times and determining the overall earliest bloom day.

1# Reference Solution Code
2class Solution:
3    def earliestFullBloom(self, plantTime: List[int], growTime: List[int]) -> int:
4        ans = t = 0
5        for pt, gt in sorted(zip(plantTime, growTime), key=lambda x: -x[1]):
6            t += pt # day when planting of this seed is finished
7            ans = max(ans, t + gt) # latest bloom day amongst all seeds
8        return ans # the earliest possible day where all seeds are blooming

In the provided algorithm, each step is purposeful and designed to optimize the bloom time based on seed growth characteristics. By focusing on the growth period and organizing the planting schedule accordingly, we reach an optimal and efficient solution.

Discover Your Strengths and Weaknesses: Take Our 2-Minute Quiz to Tailor Your Study Plan:

How many times is a tree node visited in a depth first search?

Example Walkthrough

Let's consider a small example to illustrate the solution approach:

Suppose we are given the following plantTime and growTime for n = 3 seeds:

  • Seed 1: plantTime[0] = 1, growTime[0] = 4
  • Seed 2: plantTime[1] = 2, growTime[1] = 3
  • Seed 3: plantTime[2] = 3, growTime[2] = 2

Following the solution approach:

  1. Pair each seed's planting time with its growth time:
1Seed 1 -> (1, 4)
2Seed 2 -> (2, 3)
3Seed 3 -> (3, 2)
  1. Sort the seeds in descending order of grow time:
1Sorted Pairs: 
2  Seed 1 -> (1, 4)
3  Seed 2 -> (2, 3)
4  Seed 3 -> (3, 2)

(Note: Since our example is already in descending order, no changes are made).

  1. Initialize t and ans to zero:
1t = 0 (Total days spent planting)
2ans = 0 (Earliest day all seeds are blooming)
  1. Iterate through the sorted list of pairs:
  • For Seed 1: pt = 1, gt = 4
1t += 1 (t = 1) 
2ans = max(ans, t + gt) = max(0, 1 + 4) = 5
  • For Seed 2: pt = 2, gt = 3
1t += 2 (t = 3)
2ans = max(ans, t + gt) = max(5, 3 + 3) = 6
  • For Seed 3: pt = 3, gt = 2
1t += 3 (t = 6)
2ans = max(ans, t + gt) = max(6, 6 + 2) = 8
  1. At the end of the iteration, ans is 8, which means all seeds will be blooming by day 8.

In this example, prioritizing the planting of seeds with the largest growTime allowed the seeds to start growing earlier. Even though seeds with shorter plantTime could have been planted first, since we prioritize by growTime, we minimize the overall waiting for flowers to bloom. Thus, following the optimal planting order, all seeds will be blooming by day 8.

Solution Implementation

1from typing import List
2
3class Solution:
4    def earliestFullBloom(self, plant_time: List[int], grow_time: List[int]) -> int:
5        # Initialize time counters for current time `curr_time` and max time `max_time`.
6        max_time = curr_time = 0
7
8        # Pair plant and grow times, then sort by grow time in descending order.
9        # This ensures that the plants with the longest grow time get planted first.
10        for planting_time, growth_time in sorted(zip(plant_time, grow_time), key=lambda x: -x[1]):
11            # Accumulate the current time by adding the planting time.
12            curr_time += planting_time
13            # Calculate the final full bloom time for the current plant and update `max_time`.
14            # This is done by taking the sum of the current time and the grow time.
15            max_time = max(max_time, curr_time + growth_time)
16
17        # Return the maximum time it will take for all plants to fully bloom.
18        return max_time
19
1class Solution {
2    public int earliestFullBloom(int[] plantTime, int[] growTime) {
3        int numPlants = plantTime.length; // Get the number of plants.
4        Integer[] indices = new Integer[numPlants]; // Create an array to store the indices of plants.
5        for (int i = 0; i < numPlants; i++) {
6            indices[i] = i; // Initialize indices with the index of each plant.
7        }
8      
9        // Sort the array of indices based on the grow time in descending order.
10        // The comparison function takes two indices and sorts them according to the grow time of their corresponding plants.
11        Arrays.sort(indices, (i, j) -> growTime[j] - growTime[i]);
12      
13        int maxDays = 0; // This will store the maximum number of days needed for all plants to reach full bloom.
14        int totalPlantTime = 0; // This will accumulate the total days spent planting thus far.
15      
16        // Iterate over the sorted array of indices.
17        for (int i : indices) {
18            // Add the time it takes to plant the current plant.
19            totalPlantTime += plantTime[i];
20            // Calculate the total days needed for this plant to fully bloom,
21            // which is the sum of the days spent planting up to now and this plant's grow time.
22            // Update maxDays to be the maximum of itself and the total bloom days of the current plant.
23            maxDays = Math.max(maxDays, totalPlantTime + growTime[i]);
24        }
25      
26        // Return the maximum number of days needed for all plants to reach full bloom.
27        return maxDays;
28    }
29}
30
1class Solution {
2public:
3    int earliestFullBloom(vector<int>& plantTimes, vector<int>& growTimes) {
4        // Get number of plants
5        int numPlants = plantTimes.size();
6
7        // Create an index vector to sort the plants based on growTime
8        vector<int> indices(numPlants);
9        iota(indices.begin(), indices.end(), 0);
10
11        // Sort the indices based on grow times (descending order)
12        // So that plants with longer grow times are planted first
13        sort(indices.begin(), indices.end(), [&](int i, int j) {
14            return growTimes[i] > growTimes[j];
15        });
16
17        // Initialize variables to track the current day and the earliest
18        // day that all plants will be in full bloom
19        int earliestBloomDay = 0;
20        int currentDay = 0;
21
22        // Iterate over the plants in the order determined by indices
23        for (int idx : indices) {
24            // Plant the current plant; this increments current planting day
25            currentDay += plantTimes[idx];
26
27            // Determine the earliest day all plants will be in full bloom
28            // This is the maximum of current day + grow time of current plant
29            // and the previous earliest bloom day
30            earliestBloomDay = max(earliestBloomDay, currentDay + growTimes[idx]);
31        }
32
33        // Return the earliest day when all plants will be in full bloom
34        return earliestBloomDay;
35    }
36};
37
1// Function to determine the earliest day when all flowers can bloom 
2// by planning the optimal order in which to plant the flowers.
3function earliestFullBloom(plantTimes: number[], growTimes: number[]): number {
4    // Number of flowers
5    const flowerCount = plantTimes.length;
6  
7    // Create an array of indices representing the flowers
8    const flowerIndices: number[] = Array.from({ length: flowerCount }, (_, index) => index);
9  
10    // Sort the indices based on the growing time in descending order
11    // so that we plant the flowers with the longest growing time first.
12    flowerIndices.sort((indexA, indexB) => growTimes[indexB] - growTimes[indexA]);
13  
14    // Initialize variables:
15    // 'totalTime' to track the total time elapsed,
16    // 'maxBloomTime' to keep the maximum bloom time so far.
17    let totalTime = 0;
18    let maxBloomTime = 0;
19  
20    // Iterate over the sorted indices array
21    for (const flowerIndex of flowerIndices) {
22        // Increment the total time by the current flower's planting time
23        totalTime += plantTimes[flowerIndex];
24        // Update the maximum bloom time
25        maxBloomTime = Math.max(maxBloomTime, totalTime + growTimes[flowerIndex]);
26    }
27  
28    // Return the maximum bloom time 
29    // which is the earliest day when all flowers will bloom
30    return maxBloomTime;
31}
32
Not Sure What to Study? Take the 2-min Quiz:

Depth first search can be used to find whether two components in a graph are connected.

Time and Space Complexity

The given Python function earliestFullBloom calculates the earliest day on which all plants will be in full bloom. It does so by first sorting the combined planting and growing times in descending order of growing time, then iterating through them to calculate the total time required.

Time Complexity

The time complexity of the function is determined by the sorted function and the subsequent iteration through the sorted list.

  1. Sorting the zipped list sorted(zip(plantTime, growTime), key=lambda x: -x[1]) has a time complexity of O(n log n), where n represents the number of elements in plantTime or growTime lists.
  2. The following for loop iterates through each element once, which has a time complexity of O(n).

Combining these two steps, the overall time complexity is dominated by the sorting step, making it O(n log n).

Space Complexity

The space complexity of the function is determined by the additional space used by the sorted list and the variables inside the function.

  1. The sorted list creates a new list of pairs from plantTime and growTime, having space complexity O(n).
  2. Variables ans, t, pt, and gt use constant space O(1).

Hence, the total space complexity of the function is O(n) for storing the sorted list.

Learn more about how to find time and space complexity quickly using problem constraints.

Fast Track Your Learning with Our Quick Skills Quiz:

Which of the two traversal algorithms (BFS and DFS) can be used to find whether two nodes are connected?


Recommended Readings


Got a question? Ask the Teaching Assistant anything you don't understand.

Still not clear? Ask in the Forum,  Discord or Submit the part you don't understand to our editors.


TA 👨‍🏫