2214. Minimum Health to Beat Game


Problem Description

In this game, you're facing a series of n levels where each level has a certain amount of damage associated with it, provided in the damage array. The damage[i] denotes how much health you will lose when you complete level i. Additionally, you have armor that can be used once to absorb some damage, up to the value of armor. However, the armor can't prevent more damage than the amount it's worth. Your goal is to find the minimum starting health that enables you to finish all levels. The important points to consider are:

  • You must go through the levels in sequence from 0 to n - 1.
  • Your health should always be greater than 0 to continue the game.
  • The armor is a one-time use mechanism that can absorb damage up to its value on any one level of your choice.

The challenge is to strategically use the armor on a level to minimize the health you must start with in order to complete all the levels.

Intuition

To solve this problem, we take the following steps:

  1. Calculate the total amount of damage that will be taken when passing all levels. This can be found by summing up all values in the damage array.
  2. Decide when to use the armor. Ideally, to minimize the starting health, the armor should be used on the level where it will prevent the most damage. This implies that the armor should be used on the level with the maximum damage value.
  3. Calculate the effective armor value. Since you cannot absorb more damage than the armor value, the effective protection from the armor is the minimum of the maximum damage among all levels and the armor value itself.
  4. Subtract this calculated effective armor value from the total damage to obtain the actual damage you need to absorb with your health.
  5. Since your health must always be greater than 0, simply add 1 to the total actual damage to determine the minimum health needed to start the game.

Bringing all this together, the solution efficiently calculates the minimum health needed to beat the game with the given constraints.

Learn more about Greedy and Prefix Sum patterns.

Solution Approach

The implementation of the provided solution uses simple calculation and manipulation of the data structures given, which are a list for the damage and an integer for armor.

  1. Summing Damage: The sum() function is used to calculate the total sum of the elements within the damage array, which represents the total damage you'll incur if you don't use the armor.
total_damage = sum(damage)
  1. Optimal Use of Armor: To make the best use of the armor, we figure out the maximum damage that could be absorbed. This is found by identifying the maximum damage from the levels using max(damage). However, since the armor cannot absorb more than its own value, we use min(max(damage), armor) to determine the actual damage that will be mitigated by the armor.
effective_armor_protection = min(max(damage), armor)
  1. Actual Damage Incurred: Now, we subtract the effective armor protection (the actual damage the armor can absorb) from the total calculated damage.
actual_damage_incurred = total_damage - effective_armor_protection
  1. Minimum Health Calculation: Ultimately, since health must always be above 0, the minimum health needed to beat the game, factoring in the optimal use of armor, is found by adding 1 to the actual_damage_incurred.
min_health = actual_damage_incurred + 1

The above steps are compactly expressed in the given solution:

def minimumHealth(self, damage: List[int], armor: int) -> int:
    return sum(damage) - min(max(damage), armor) + 1

This algorithm is O(n) given that finding the maximum in an array damage and summing its elements both take O(n) time. No additional data structures are used besides the input, making it a space-efficient approach as well.

Note: In the code implementation, these steps are combined into one line for efficiency and brevity, but they conceptually represent the algorithm's workflow described above.

Ready to land your dream job?

Unlock your dream job with a 2-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

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

Suppose we have the following damage array and armor value:

  • damage = [5, 10, 7]
  • armor = 10

According to our game's rules and solution approach, here's what we do:

  1. Summing Damage: We add up all the damage values in the array:
total_damage = 5 + 10 + 7 = 22
  1. Optimal Use of Armor: The maximum damage in a single level is 10. Since our armor value is equal to the maximum damage, armor can completely absorb the damage from this level. So, the effective armor protection is:
effective_armor_protection = min(10, 10) = 10
  1. Actual Damage Incurred: Now, we calculate the actual damage you incur by subtracting the armor's protection from the total damage:
actual_damage_incurred = 22 - 10 = 12
  1. Minimum Health Calculation: To ensure your health is always greater than 0, you need to start with one more health point than the actual damage incurred. Hence, the minimum health required would be:
min_health = 12 + 1 = 13

In this example, you would need a minimum of 13 health to complete all levels of the game, using your armor optimally to fully absorb the damage from the level with the highest damage.

Solution Implementation

1class Solution:
2    def minimumHealth(self, damage: List[int], armor: int) -> int:
3        """
4        Calculate the minimum health required to survive a series of damage hits,
5        given that armor can completely absorb the damage from one hit.
6      
7        :param damage: List[int] indicating the damage value of each hit
8        :param armor: int representing the value of the armor
9        :return: int minimum health needed to survive all hits
10        """
11      
12        # Calculate total damage that would be taken without armor
13        total_damage = sum(damage)
14      
15        # Determine the maximum single hit damage
16        max_damage = max(damage)
17      
18        # Calculate the effective damage reduction by armor, capped at the max hit
19        # This ensures that the armor does not absorb more than the heaviest hit
20        effective_armor = min(max_damage, armor)
21      
22        # Calculate the minimum health required:
23        # It's the total damage minus the effective armor value, plus one.
24        # Adding one because health needs to be at least 1 to survive. 
25        minimum_health = total_damage - effective_armor + 1
26      
27        return minimum_health
28
1class Solution {
2    public long minimumHealth(int[] damage, int armor) {
3        // Initialize the sum of all damage values to 0
4        long totalDamage = 0;
5        // Initialize the variable to hold the maximum single hit damage
6        int maxDamage = damage[0];
7      
8        // Iterate over the damage array to calculate the total damage
9        // and find the maximum single hit damage
10        for (int currentDamage : damage) {
11            totalDamage += currentDamage;
12            maxDamage = Math.max(maxDamage, currentDamage);
13        }
14
15        // Calculate the minimum health required to survive all the damage,
16        // taking into account the armor which can absorb damage not exceeding the value of the strongest hit.
17        // Add 1 to ensure that the health is strictly above 0 after taking all damage.
18        return totalDamage - Math.min(maxDamage, armor) + 1;
19    }
20}
21
1class Solution {
2public:
3    // Calculate the minimum health required to defeat the monsters.
4    // damage - list of integers representing the damage of each monster.
5    // armor - an integer representing the maximum damage the armor can absorb.
6    long long minimumHealth(vector<int>& damage, int armor) {
7        long long totalDamage = 0; // To store the sum of all damages.
8        int maxDamage = damage[0]; // Initialize maxDamage with the first element.
9
10        // Iterate over the damage array to find the sum of all damages
11        // and the maximum damage dealt by any single monster.
12        for (int dmg : damage) {
13            totalDamage += dmg;     // Accumulate the total damage.
14            maxDamage = max(maxDamage, dmg); // Keep track of the max damage.
15        }
16
17        // Calculate and return the minimum health needed:
18        // Subtract the smaller of the max damage or the armor from total damage
19        // and add 1 (since the health must be at least 1 to survive).
20        return totalDamage - min(maxDamage, armor) + 1;
21    }
22};
23
1// Function to calculate the minimum health required to withstand damage with the potential armor reduction applied
2function minimumHealth(damage: number[], armor: number): number {
3    let totalDamage = 0;  // Initialize the total damage taken to zero
4    let maxDamage = 0;    // Initialize the maximum single hit damage to zero
5  
6    // Iterate over the array of damage values
7    for (const singleDamage of damage) {
8        // Update the maximum single hit damage if the current one is greater
9        maxDamage = Math.max(maxDamage, singleDamage);
10        // Sum up the total damage taken
11        totalDamage += singleDamage;
12    }
13  
14    // Calculate minimum health required:
15    // Total damage minus the lesser of max damage or armor (the armor can only reduce up to the max damage taken in a single hit)
16    // We add 1 because health must be at least 1 more than the remaining damage after armor reduction
17    return totalDamage - Math.min(maxDamage, armor) + 1;
18}
19

Time and Space Complexity

  • Time Complexity: The time complexity of the method minimumHealth is O(n), where n is the length of the damage list. This is because the functions sum(damage), min(damage), and max(damage) each require traversing the entire list once. No other operations inside the method have a higher time complexity, so the overall complexity does not exceed linear time.

  • Space Complexity: The space complexity of the method minimumHealth is O(1). This is because the space required does not depend on the size of the input list damage. The memory used is constant as we only need a fixed amount of space to store intermediate results such as the sum of the damage list, the minimum value, the maximum value, and the final health calculation.

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


Discover Your Strengths and Weaknesses: Take Our 2-Minute Quiz to Tailor Your Study Plan:
Question 1 out of 10

What does the following code do?

1def f(arr1, arr2):
2  i, j = 0, 0
3  new_arr = []
4  while i < len(arr1) and j < len(arr2):
5      if arr1[i] < arr2[j]:
6          new_arr.append(arr1[i])
7          i += 1
8      else:
9          new_arr.append(arr2[j])
10          j += 1
11  new_arr.extend(arr1[i:])
12  new_arr.extend(arr2[j:])
13  return new_arr
14
1public static List<Integer> f(int[] arr1, int[] arr2) {
2  int i = 0, j = 0;
3  List<Integer> newArr = new ArrayList<>();
4
5  while (i < arr1.length && j < arr2.length) {
6      if (arr1[i] < arr2[j]) {
7          newArr.add(arr1[i]);
8          i++;
9      } else {
10          newArr.add(arr2[j]);
11          j++;
12      }
13  }
14
15  while (i < arr1.length) {
16      newArr.add(arr1[i]);
17      i++;
18  }
19
20  while (j < arr2.length) {
21      newArr.add(arr2[j]);
22      j++;
23  }
24
25  return newArr;
26}
27
1function f(arr1, arr2) {
2  let i = 0, j = 0;
3  let newArr = [];
4  
5  while (i < arr1.length && j < arr2.length) {
6      if (arr1[i] < arr2[j]) {
7          newArr.push(arr1[i]);
8          i++;
9      } else {
10          newArr.push(arr2[j]);
11          j++;
12      }
13  }
14  
15  while (i < arr1.length) {
16      newArr.push(arr1[i]);
17      i++;
18  }
19  
20  while (j < arr2.length) {
21      newArr.push(arr2[j]);
22      j++;
23  }
24  
25  return newArr;
26}
27

Recommended Readings

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