Facebook Pixel

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 hour i
  • f[i][1]: Maximum energy boost achievable by choosing drink B at hour i

At each hour, you can either:

  1. Continue with the same drink from the previous hour (add current hour's energy to previous total)
  2. 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].

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 add energyDrinkA[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
  • Similarly, if we want to drink B at hour i:

    • We were already drinking B at hour i-1: add energyDrinkB[i] to our previous total
    • We were drinking A at hour i-1: switch and skip this hour's energy

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 hour i
  • f[i][1]: Maximum energy boost obtainable by choosing drink B at hour i

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:

  1. Create a 2D array f of size n × 2 to store the DP states
  2. Initialize the base cases for hour 0
  3. Iterate through hours 1 to n-1, applying the transition formulas
  4. 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 Evaluator

Example 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.

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

Which technique can we use to find the middle of a linked list?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More