1665. Minimum Initial Energy to Finish Tasks
Problem Description
In this task, we are given an array called tasks
, with each element being a pair of values [actual_i, minimum_i]
. Each pair represents a task where actual_i
is the amount of energy needed to complete the i
-th task and minimum_i
is the minimum amount of energy required to start the task. The challenge is to figure out the least amount of energy we need initially to be able to complete all tasks, but what's unique is that we can tackle the tasks in any sequence we prefer.
Imagine having some tasks that need a significant amount of energy to start but consume less upon completion, while others are easy to start but require a lot of energy to complete. The optimal sequence to complete these tasks will ensure that you never find yourself short of energy before starting a task. Given this, we are asked to calculate the minimum initial energy.
Intuition
To arrive at an intuitive solution, consider that a task requiring a lot of energy to begin but not as much to finish should be tackled later than a task that consumes more energy than it requires to start. This is because finishing tasks that spend more energy than the minimum required will deplete the energy reserve, potentially preventing the start of tasks with high initial energy requirements.
Hence, the solution sorts the tasks by the difference between actual_i
and minimum_i
. The tasks with the smallest difference (even potentially negative) are placed first, since completing these tasks either depletes the least energy from the reserve or can even recharge it.
While iterating over the sorted tasks, the algorithm checks if the current energy level (cur
) is sufficient to start the current task (m
). If it's not enough, we need only increase our initial energy just enough to start the task (m - cur
). We then deduct the actual energy spent to complete the task from cur
. Throughout this process, ans
tracks the total additional energy required, if any, which ultimately will be the minimum initial energy needed to complete all tasks.
Solution Approach
The solution makes use of the sort function, a basic algorithm in Python, to arrange the tasks such that the task with the smallest difference between the actual energy and the minimum required energy needed to start comes first. This is done to prioritize tasks that will either decrease our energy the least or could potentially increase it (if actual_i
< minimum_i
).
To understand this, let's assume we have a list of tasks, each represented as [actual_i, minimum_i]
. We apply the sorted
function with a custom sort key — the difference x[0] - x[1]
(actual_i - minimum_i
), which helps us to process tasks that are less energy-consuming first.
The data structure used here is a simple list of lists, a common choice for representing a sequence of elements where each element itself contains multiple pieces of data.
The algorithm follows these steps:
- Initialize
ans
andcur
to zero.ans
will hold our final answer, andcur
tracks the current energy level. - Sort the tasks by the difference (
x[0] - x[1]
), which is calculated for each task using a lambda function as the key in thesorted
method. - Iterate through the sorted tasks list.
- For each task, check if our current energy
cur
is less than the minimum required energym
. - If so, we update
ans
with the differencem - cur
, as we need to boost our energy level up to the minimum required to start the task. We also updatecur
to reflect this new level of energy. - Deduct the actual amount of energy spent on the task
a
fromcur
.
- For each task, check if our current energy
- The loop exits after all tasks have been processed, and
ans
now holds the minimum initial amount of energy we need to finish all the tasks without running out of energy at any point.
Using this approach allows us to guarantee that we always have enough energy to start each task by incrementing our initial energy only when necessary and by the smallest amount needed. After all tasks are completed, ans
tells us the minimum energy we would need to reserve initially to accomplish this.
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 say we're given the following array of tasks: [[3,1], [2,2], [1,2]]
. Each task is represented as [actual_i, minimum_i]
.
-
We start by initializing
ans
andcur
to zero. These variables will help us keep track of the additional energy needed (ans
) and the current energy level (cur
). -
We need to sort the tasks by the difference between
actual_i
andminimum_i
. Using a lambda function as the key in thesorted
method, the tasks are sorted as follows:[[1,2], [2,2], [3,1]]
→ The task[1,2]
has a difference of-1
(it actually gives us energy),[2,2]
breaks even, and[3,1]
requires 2 units more energy than it gives back. -
We iterate through the sorted tasks:
-
The first task is
[1, 2]
.cur
is 0 and is less than the minimum required energy2
for the task. We need to increase it by2-0=2
, soans
becomes2
andcur
becomes2
. After completing the task that consumes1
unit of energy,cur
is now2-1=1
. -
The next task is
[2, 2]
.cur
is now1
which is less than the2
required to start the task. We must increasecur
by2-1=1
, which meansans
is updated from2
to2+1=3
. After finishing the task that uses2
units of energy,cur
becomes1-2=-1
. (This indicates that we finished the task using not just the current energy we had, but also some of the initial energy we reserved.) -
The final task is
[3, 1]
.cur
is-1
, and since the minimum energy to start is1
, we will boostcur
by1-(-1)=2
, makingans
now3+2=5
. We then complete the task by spending3
units of energy, andcur
is updated to-1-3=-4
.
-
-
Having processed all tasks,
ans
holds the minimum initial amount of energy we need to complete all of them. In this case, we must start with at least5
units of energy to ensure we can complete the tasks in this specific order.
By carefully selecting the sequence of the tasks and calculating the increments in our energy reserve only as needed, we find that starting with 5 units of energy allows us to complete all tasks successfully.
Solution Implementation
1from typing import List
2
3class Solution:
4 def minimumEffort(self, tasks: List[List[int]]) -> int:
5 # Initialize the answer and the current energy to zero.
6 energy_needed = current_energy = 0
7
8 # Sort the tasks based on the difference between minimum energy needed
9 # and actual energy consumed (i.e., sort by actual effort required).
10 # We iterate through each sorted task.
11 for actual, minimum in sorted(tasks, key=lambda x: x[0] - x[1]):
12 # If current energy is less than the minimum energy needed for
13 # the task, we need to increase our energy to at least the minimum.
14 if current_energy < minimum:
15 # Update the total energy needed by adding the difference
16 # between the minimum energy needed and the current energy.
17 energy_needed += minimum - current_energy
18 # Set the current energy to the minimum required
19 current_energy = minimum
20 # After completing the task, decrease the current energy by
21 # the amount of actual energy consumed.
22 current_energy -= actual
23
24 # Return the total energy needed to complete all tasks.
25 return energy_needed
26
27# Example usage:
28# tasks = [[1, 2], [2, 4], [4, 8]]
29# solution = Solution()
30# print(solution.minimumEffort(tasks)) # Output will be the total minimum energy needed
31
1import java.util.Arrays; // Import Arrays utility for sorting
2
3class Solution {
4
5 // Calculates the minimum initial energy required to complete all tasks
6 public int minimumEffort(int[][] tasks) {
7 // Sort the tasks by the difference between the minimum energy to start and actual energy consumed
8 Arrays.sort(tasks, (a, b) -> (b[1] - b[0]) - (a[1] - a[0]));
9
10 int totalEnergyNeeded = 0; // Total initial energy needed to start all tasks
11 int currentEnergy = 0; // Current energy level while performing tasks
12
13 // Iterate over each task to calculate the total initial energy required
14 for (int[] task : tasks) {
15 int actualEnergy = task[0]; // Actual energy spent to complete the task
16 int minimumEnergyToStart = task[1]; // Minimum energy required to initiate the task
17
18 // If current energy is less than the minimum required to start the task,
19 // increase the total energy needed accordingly and update the current energy
20 if (currentEnergy < minimumEnergyToStart) {
21 totalEnergyNeeded += minimumEnergyToStart - currentEnergy;
22 currentEnergy = minimumEnergyToStart;
23 }
24
25 // After task completion, reduce the current energy by the actual energy spent
26 currentEnergy -= actualEnergy;
27 }
28
29 // Return the calculated total initial energy needed
30 return totalEnergyNeeded;
31 }
32}
33
1#include <vector>
2#include <algorithm> // Needed to use the std::sort function
3
4class Solution {
5public:
6 int minimumEffort(std::vector<std::vector<int>>& tasks) {
7 // Sort the tasks based on the difference between the minimum initial energy required
8 // and the actual energy consumed by the task. The task with the smallest difference
9 // comes first because it requires less effort after completion to start the next task.
10 std::sort(tasks.begin(), tasks.end(), [&](const auto& task1, const auto& task2) {
11 return (task1[0] - task1[1]) < (task2[0] - task2[1]);
12 });
13
14 int totalEffort = 0; // Accumulated effort required to start all tasks
15 int currentEnergy = 0; // Current available energy
16
17 // Iterate through the sorted tasks
18 for (auto& task : tasks) {
19 int actualEnergyConsumption = task[0]; // Actual energy consumed by the task
20 int minInitialEnergy = task[1]; // Minimum initial energy required to start the task
21
22 // If currentEnergy is less than the minimum required initial energy
23 // then increase the totalEffort by the difference and update currentEnergy
24 if (currentEnergy < minInitialEnergy) {
25 totalEffort += minInitialEnergy - currentEnergy;
26 currentEnergy = minInitialEnergy;
27 }
28
29 // After the task is done, subtract the actual energy consumption from the
30 // current energy to represent the new state of current energy
31 currentEnergy -= actualEnergyConsumption;
32 }
33
34 // Return the accumulated totalEffort required to start all tasks
35 return totalEffort;
36 }
37};
38
1function minimumEffort(tasks: number[][]): number {
2 // Sort the tasks based on the difference between minimum initial energy and actual energy consumption
3 // This helps to start with tasks that require less extra energy after considering their actual energy use
4 tasks.sort((task1, task2) => (task1[0] - task1[1]) - (task2[0] - task2[1]));
5
6 let totalMinimumEnergy = 0; // The accumulated energy needed to complete all tasks
7 let currentEnergy = 0; // Current energy level
8
9 // Iterate over each task to calculate the minimum initial energy required to complete them
10 for (const [actualEnergy, minimumInitialEnergy] of tasks) {
11 // If the current energy is less than the task's minimum initial required energy
12 if (currentEnergy < minimumInitialEnergy) {
13 // Need to add the extra energy required to meet the minimum initial energy
14 totalMinimumEnergy += minimumInitialEnergy - currentEnergy;
15 // Update the current energy to match the minimum initial requirement
16 currentEnergy = minimumInitialEnergy;
17 }
18 // After completing the task, the actual energy used is subtracted from the current energy
19 currentEnergy -= actualEnergy;
20 }
21
22 // Return the accumulated minimum initial energy needed to complete all tasks
23 return totalMinimumEnergy;
24}
25
Time and Space Complexity
Time Complexity
The time complexity of the solution is dominated by the sorting of the tasks array and the subsequent iteration through the sorted tasks.
The sorting function uses the Timsort algorithm in Python, which has a time complexity of O(n log n)
in the average and worst case, where n
is the number of tasks. After sorting, the function goes through the tasks one by one, which takes O(n)
time.
Therefore, combining both steps, the total time complexity of the function is O(n log n)
due to the sorting step being the most significant factor.
Space Complexity
The space complexity of the solution can be considered as O(1)
because the sorting is done in-place and no additional data structures that grow with the input size are used. The variables ans
, cur
, a
, and m
are all constant in space, requiring a fixed amount of memory regardless of the number of tasks.
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 56
?
1KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12def letter_combinations_of_phone_number(digits):
13 def dfs(path, res):
14 if len(path) == len(digits):
15 res.append(''.join(path))
16 return
17
18 next_number = digits[len(path)]
19 for letter in KEYBOARD[next_number]:
20 path.append(letter)
21 dfs(path, res)
22 path.pop()
23
24 res = []
25 dfs([], res)
26 return res
27
1private static final Map<Character, char[]> KEYBOARD = Map.of(
2 '2', "abc".toCharArray(),
3 '3', "def".toCharArray(),
4 '4', "ghi".toCharArray(),
5 '5', "jkl".toCharArray(),
6 '6', "mno".toCharArray(),
7 '7', "pqrs".toCharArray(),
8 '8', "tuv".toCharArray(),
9 '9', "wxyz".toCharArray()
10);
11
12public static List<String> letterCombinationsOfPhoneNumber(String digits) {
13 List<String> res = new ArrayList<>();
14 dfs(new StringBuilder(), res, digits.toCharArray());
15 return res;
16}
17
18private static void dfs(StringBuilder path, List<String> res, char[] digits) {
19 if (path.length() == digits.length) {
20 res.add(path.toString());
21 return;
22 }
23 char next_digit = digits[path.length()];
24 for (char letter : KEYBOARD.get(next_digit)) {
25 path.append(letter);
26 dfs(path, res, digits);
27 path.deleteCharAt(path.length() - 1);
28 }
29}
30
1const KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12function letter_combinations_of_phone_number(digits) {
13 let res = [];
14 dfs(digits, [], res);
15 return res;
16}
17
18function dfs(digits, path, res) {
19 if (path.length === digits.length) {
20 res.push(path.join(''));
21 return;
22 }
23 let next_number = digits.charAt(path.length);
24 for (let letter of KEYBOARD[next_number]) {
25 path.push(letter);
26 dfs(digits, path, res);
27 path.pop();
28 }
29}
30
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
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
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!