3259. Maximum Energy Boost From Two Drinks
Problem Description
You have two energy drinks, A and B, represented by two integer arrays energyDrinkA
and energyDrinkB
of the same length n
. Each element in these arrays represents the energy boost you get from drinking that energy drink at that specific hour.
Your goal is to maximize your total energy boost over n
hours by drinking exactly one energy drink per hour. However, there's an important constraint: if you switch from one energy drink to the other, you must wait one hour for your system to cleanse. During this cleansing hour, you don't get any energy boost at all.
For example:
- If you drink A at hour 0 and want to switch to B at hour 1, you get 0 energy at hour 1 (cleansing hour) and can start drinking B from hour 2
- If you drink A at hour 0 and continue with A at hour 1, you get the full energy boost from A at hour 1
You can start with either energy drink A or B. The problem asks you to find the maximum total energy boost possible over all n
hours.
The dynamic programming solution tracks two states at each hour:
f[i][0]
: Maximum energy boost achievable by choosing drink A at houri
f[i][1]
: Maximum energy boost achievable by choosing drink B at houri
At each hour, you can either:
- Continue with the same drink from the previous hour (add current hour's energy to previous total)
- Switch from the other drink (skip current hour's energy due to cleansing, so take only the previous hour's total from the other drink)
The final answer is the maximum between f[n-1][0]
and f[n-1][1]
.
Intuition
The key insight is that at any given hour, we need to make a decision: which drink should we consume? This decision depends on what we did in the previous hour because of the switching penalty.
Think of this problem as making optimal choices at each step while considering the consequences of our previous choice. At each hour i
, we're essentially in one of two states: either we're drinking A or we're drinking B. The crucial observation is that our maximum energy at hour i
depends only on our maximum energy at hour i-1
, not on the entire history of our choices.
This naturally leads to a dynamic programming approach where we track the best possible outcome for each state at each hour. For any hour i
:
-
If we want to drink A at hour
i
, we have two options:- We were already drinking A at hour
i-1
: we simply addenergyDrinkA[i]
to our previous total - We were drinking B at hour
i-1
: we switch, meaning we skip this hour's energy (cleansing), so we only carry forward the previous total from B
- We were already drinking A at hour
-
Similarly, if we want to drink B at hour
i
:- We were already drinking B at hour
i-1
: addenergyDrinkB[i]
to our previous total - We were drinking A at hour
i-1
: switch and skip this hour's energy
- We were already drinking B at hour
The beauty of this approach is that we don't need to explicitly track when we're cleansing. The cleansing penalty is naturally incorporated when we take the maximum between continuing with the same drink (getting energy) versus switching from the other drink (no energy for this hour).
By maintaining f[i][0]
and f[i][1]
to represent the maximum energy when choosing drink A or B at hour i
respectively, we can build up the solution hour by hour, always choosing the option that gives us the better total energy at each state.
Learn more about Dynamic Programming patterns.
Solution Approach
We use dynamic programming with a 2D array to track the maximum energy at each hour for each drink choice.
State Definition:
f[i][0]
: Maximum energy boost obtainable by choosing drink A at houri
f[i][1]
: Maximum energy boost obtainable by choosing drink B at houri
Initialization: At hour 0, we can start with either drink without any penalty:
f[0][0] = energyDrinkA[0]
f[0][1] = energyDrinkB[0]
State Transition:
For each hour i
from 1 to n-1
, we calculate the maximum energy for each drink choice:
For choosing drink A at hour i
:
f[i][0] = max(f[i-1][0] + energyDrinkA[i], f[i-1][1])
- First option: Continue with A from previous hour and add current energy
- Second option: Switch from B (no energy gained this hour due to cleansing)
For choosing drink B at hour i
:
f[i][1] = max(f[i-1][1] + energyDrinkB[i], f[i-1][0])
- First option: Continue with B from previous hour and add current energy
- Second option: Switch from A (no energy gained this hour due to cleansing)
Implementation Details:
- Create a 2D array
f
of sizen × 2
to store the DP states - Initialize the base cases for hour 0
- Iterate through hours 1 to
n-1
, applying the transition formulas - Return the maximum of the two final states:
max(f[n-1][0], f[n-1][1])
The time complexity is O(n)
as we process each hour once, and the space complexity is O(n)
for storing the DP array. This could be optimized to O(1)
space since we only need the previous hour's values to compute the current hour's values.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example to illustrate the solution approach.
Example Input:
energyDrinkA = [3, 1, 4, 2]
energyDrinkB = [5, 2, 1, 3]
n = 4
hours
We'll build our DP table step by step, where f[i][0]
represents the maximum energy when choosing drink A at hour i, and f[i][1]
represents the maximum energy when choosing drink B at hour i.
Hour 0 (Initialization):
f[0][0] = 3
(start with drink A, get 3 energy)f[0][1] = 5
(start with drink B, get 5 energy)
Hour 1: For drink A at hour 1:
- Option 1: Continue with A from hour 0:
f[0][0] + energyDrinkA[1] = 3 + 1 = 4
- Option 2: Switch from B (cleansing, no energy):
f[0][1] = 5
f[1][0] = max(4, 5) = 5
For drink B at hour 1:
- Option 1: Continue with B from hour 0:
f[0][1] + energyDrinkB[1] = 5 + 2 = 7
- Option 2: Switch from A (cleansing, no energy):
f[0][0] = 3
f[1][1] = max(7, 3) = 7
Hour 2: For drink A at hour 2:
- Option 1: Continue with A:
f[1][0] + energyDrinkA[2] = 5 + 4 = 9
- Option 2: Switch from B:
f[1][1] = 7
f[2][0] = max(9, 7) = 9
For drink B at hour 2:
- Option 1: Continue with B:
f[1][1] + energyDrinkB[2] = 7 + 1 = 8
- Option 2: Switch from A:
f[1][0] = 5
f[2][1] = max(8, 5) = 8
Hour 3: For drink A at hour 3:
- Option 1: Continue with A:
f[2][0] + energyDrinkA[3] = 9 + 2 = 11
- Option 2: Switch from B:
f[2][1] = 8
f[3][0] = max(11, 8) = 11
For drink B at hour 3:
- Option 1: Continue with B:
f[2][1] + energyDrinkB[3] = 8 + 3 = 11
- Option 2: Switch from A:
f[2][0] = 9
f[3][1] = max(11, 9) = 11
Final Answer:
max(f[3][0], f[3][1]) = max(11, 11) = 11
The optimal strategy achieves a total energy of 11. One possible sequence is: B(5) → B(2) → A(0, cleansing) → A(4) = 11 total energy. Another is: B(5) → cleansing(0) → A(4) → A(2) = 11 total energy.
Solution Implementation
1class Solution:
2 def maxEnergyBoost(self, energyDrinkA: List[int], energyDrinkB: List[int]) -> int:
3 n = len(energyDrinkA)
4
5 # dp[i][j] represents the maximum energy at hour i
6 # j=0 means ending with drink A, j=1 means ending with drink B
7 dp = [[0] * 2 for _ in range(n)]
8
9 # Base case: at hour 0, we can choose either drink
10 dp[0][0] = energyDrinkA[0] # Choose drink A at hour 0
11 dp[0][1] = energyDrinkB[0] # Choose drink B at hour 0
12
13 # Fill the dp table for each subsequent hour
14 for i in range(1, n):
15 # Maximum energy at hour i ending with drink A
16 # Either continue with A from previous hour, or switch from B (no energy gained this hour)
17 dp[i][0] = max(dp[i - 1][0] + energyDrinkA[i], dp[i - 1][1])
18
19 # Maximum energy at hour i ending with drink B
20 # Either continue with B from previous hour, or switch from A (no energy gained this hour)
21 dp[i][1] = max(dp[i - 1][1] + energyDrinkB[i], dp[i - 1][0])
22
23 # Return the maximum energy at the last hour, considering both drinks
24 return max(dp[n - 1])
25
1class Solution {
2 public long maxEnergyBoost(int[] energyDrinkA, int[] energyDrinkB) {
3 int n = energyDrinkA.length;
4
5 // dp[i][j] represents the maximum energy boost at hour i
6 // j = 0: ending with drink A, j = 1: ending with drink B
7 long[][] dp = new long[n][2];
8
9 // Base case: at hour 0, we can choose either drink
10 dp[0][0] = energyDrinkA[0];
11 dp[0][1] = energyDrinkB[0];
12
13 // Fill the DP table for each hour
14 for (int i = 1; i < n; i++) {
15 // Maximum energy ending at hour i with drink A:
16 // Either continue with A from previous hour, or switch from B (no energy gain this hour)
17 dp[i][0] = Math.max(dp[i - 1][0] + energyDrinkA[i], dp[i - 1][1]);
18
19 // Maximum energy ending at hour i with drink B:
20 // Either continue with B from previous hour, or switch from A (no energy gain this hour)
21 dp[i][1] = Math.max(dp[i - 1][1] + energyDrinkB[i], dp[i - 1][0]);
22 }
23
24 // Return the maximum energy boost possible at the last hour
25 return Math.max(dp[n - 1][0], dp[n - 1][1]);
26 }
27}
28
1class Solution {
2public:
3 long long maxEnergyBoost(vector<int>& energyDrinkA, vector<int>& energyDrinkB) {
4 int n = energyDrinkA.size();
5
6 // dp[i][j] represents the maximum energy boost at hour i
7 // j = 0: ending with drink A, j = 1: ending with drink B
8 vector<vector<long long>> dp(n, vector<long long>(2));
9
10 // Base case: at hour 0, we can choose either drink
11 dp[0][0] = energyDrinkA[0];
12 dp[0][1] = energyDrinkB[0];
13
14 // Fill the DP table for each hour
15 for (int i = 1; i < n; ++i) {
16 // To drink A at hour i:
17 // Option 1: Continue drinking A from previous hour (dp[i-1][0] + energyDrinkA[i])
18 // Option 2: Switch from B, but skip this hour (dp[i-1][1])
19 dp[i][0] = max(dp[i - 1][0] + energyDrinkA[i], dp[i - 1][1]);
20
21 // To drink B at hour i:
22 // Option 1: Continue drinking B from previous hour (dp[i-1][1] + energyDrinkB[i])
23 // Option 2: Switch from A, but skip this hour (dp[i-1][0])
24 dp[i][1] = max(dp[i - 1][1] + energyDrinkB[i], dp[i - 1][0]);
25 }
26
27 // Return the maximum energy boost at the last hour
28 return max(dp[n - 1][0], dp[n - 1][1]);
29 }
30};
31
1/**
2 * Calculates the maximum energy boost that can be obtained by drinking from two energy drink arrays.
3 * At each hour, you can either continue drinking from the same type or switch to the other type.
4 * If you switch, you cannot drink in that hour (penalty for switching).
5 *
6 * @param energyDrinkA - Array of energy values for drink type A
7 * @param energyDrinkB - Array of energy values for drink type B
8 * @returns The maximum total energy boost possible
9 */
10function maxEnergyBoost(energyDrinkA: number[], energyDrinkB: number[]): number {
11 const n: number = energyDrinkA.length;
12
13 // Dynamic programming table where:
14 // dp[i][0] = maximum energy up to hour i, ending with drink A
15 // dp[i][1] = maximum energy up to hour i, ending with drink B
16 const dp: number[][] = Array.from({ length: n }, () => [0, 0]);
17
18 // Base case: at hour 0, we can start with either drink
19 dp[0][0] = energyDrinkA[0];
20 dp[0][1] = energyDrinkB[0];
21
22 // Fill the DP table for each subsequent hour
23 for (let i = 1; i < n; i++) {
24 // To drink A at hour i:
25 // - Continue from A: dp[i-1][0] + energyDrinkA[i]
26 // - Switch from B: dp[i-1][1] (skip this hour due to switching)
27 dp[i][0] = Math.max(dp[i - 1][0] + energyDrinkA[i], dp[i - 1][1]);
28
29 // To drink B at hour i:
30 // - Continue from B: dp[i-1][1] + energyDrinkB[i]
31 // - Switch from A: dp[i-1][0] (skip this hour due to switching)
32 dp[i][1] = Math.max(dp[i - 1][1] + energyDrinkB[i], dp[i - 1][0]);
33 }
34
35 // Return the maximum energy between ending with drink A or drink B
36 return Math.max(dp[n - 1][0], dp[n - 1][1]);
37}
38
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the arrays energyDrinkA
and energyDrinkB
. The algorithm iterates through the arrays once with a single for loop from index 1 to n-1, performing constant-time operations (max comparisons and additions) at each iteration.
The space complexity is O(n)
. The algorithm creates a 2D array f
with dimensions n × 2
, which stores the dynamic programming states. This requires O(2n) = O(n)
space to store the maximum energy values for each position when choosing either drink A or drink B.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Misunderstanding the Switching Penalty
Pitfall: A common mistake is thinking that switching drinks means you lose energy at the next hour after the switch, rather than at the current hour of switching.
Incorrect interpretation:
# WRONG: Penalizing the next hour
dp[i][0] = max(dp[i-1][0] + energyDrinkA[i], dp[i-1][1] + energyDrinkA[i] - penalty)
Correct interpretation:
# CORRECT: No energy gained at current hour when switching
dp[i][0] = max(dp[i-1][0] + energyDrinkA[i], dp[i-1][1])
When you switch from B to A at hour i
, you get 0 energy at hour i
(not energyDrinkA[i]
), which is why we only take dp[i-1][1]
without adding anything.
2. Space Optimization Leading to Incorrect Updates
Pitfall: When optimizing space from O(n) to O(1) by using only two variables, updating them in the wrong order can overwrite values needed for subsequent calculations.
Incorrect space optimization:
# WRONG: Overwriting prevA before it's used to calculate currB
def maxEnergyBoost(self, energyDrinkA, energyDrinkB):
prevA = energyDrinkA[0]
prevB = energyDrinkB[0]
for i in range(1, len(energyDrinkA)):
prevA = max(prevA + energyDrinkA[i], prevB) # prevA updated
prevB = max(prevB + energyDrinkB[i], prevA) # Using the NEW prevA - WRONG!
return max(prevA, prevB)
Correct space optimization:
# CORRECT: Store values in temporary variables
def maxEnergyBoost(self, energyDrinkA, energyDrinkB):
prevA = energyDrinkA[0]
prevB = energyDrinkB[0]
for i in range(1, len(energyDrinkA)):
currA = max(prevA + energyDrinkA[i], prevB)
currB = max(prevB + energyDrinkB[i], prevA)
prevA, prevB = currA, currB
return max(prevA, prevB)
3. Off-by-One Errors in Hour Indexing
Pitfall: Confusion about whether the cleansing happens at hour i
or hour i+1
when switching at hour i
.
Example scenario causing confusion:
- Hour 0: Drink A (get energyDrinkA[0])
- Hour 1: Switch to B (get 0 energy - cleansing hour)
- Hour 2: Drink B (get energyDrinkB[2])
The key insight is that the cleansing happens at the exact hour you make the switch, not the hour after.
4. Incorrect Base Case Initialization
Pitfall: Initializing both drinks at hour 0 with 0 or forgetting that you must choose one drink to start.
Incorrect initialization:
# WRONG: Starting with 0 energy dp[0][0] = 0 dp[0][1] = 0
Correct initialization:
# CORRECT: You must drink something at hour 0 dp[0][0] = energyDrinkA[0] dp[0][1] = energyDrinkB[0]
The problem states you drink exactly one energy drink per hour, so at hour 0, you must choose either A or B and gain its energy.
Which technique can we use to find the middle of a linked list?
Recommended Readings
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
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 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
Want a Structured Path to Master System Design Too? Don’t Miss This!