Start learning free

LESSON 314 OF 410 · HEAP

Kill Monsters - Python Solution and Time Complexity Analysis

Practice O(n log n) Read the full explanation

You are a hero in a game, initially having HP health points and K magic potions.

You have to fight N monsters in the order they are given, knowing the damage each monster deals to you.

damage[i] represents the damage the monster deals to you when you fight them. If the damage is negative, it means your HP increases by that amount.

If you use a magic potion on a monster, it will kill the monster without you fighting or losing health points.

If your HP drops below zero, you lose the fight. If it drops to exactly zero HP, you still kill that monster, but you are left with no health, so the fight ends there. Therefore you can't win against a monster that has damage[i] > HP.

Find the maximum number of monsters you can kill, considering you are playing optimally.

Example

Input: K = 2, HP = 10,
       damage = [-3, 2, 3, -2, 8, 8, 6, 4, 3, 3]
Output: 8
Explanation: You kill the first 8 monsters by using the two potions on the 5th and 6th monsters
STEP 1 OF 10
    Ctrl + Enter to check

    PYTHON · HEAP

    Kill Monsters in Python, explained

    Understanding the Problem

    The core challenge of this problem is to maximize the number of monsters you can kill while managing your health points (HP) and the limited number of magic potions (K). The significance of this problem lies in its application to resource management and optimization scenarios, which are common in game development and real-life strategic planning.

    Potential pitfalls include mismanaging the use of potions or failing to account for the order in which monsters are fought, leading to suboptimal solutions.

    Approach

    To solve this problem, we need to consider both the damage dealt by each monster and the strategic use of potions. Here’s a step-by-step approach:

    Naive Solution

    A naive solution would involve iterating through the list of monsters and using potions whenever the damage exceeds the current HP. However, this approach is not optimal because it doesn't consider the best use of potions to maximize the number of monsters killed.

    Optimized Solution

    An optimized solution involves using a priority queue (min-heap) to keep track of the most damaging monsters. This allows us to use potions on the most damaging monsters first, thereby maximizing the number of monsters we can kill.

    Thought Process

    1. Iterate through the list of monsters. 2. If the damage is negative, add it to HP. 3. If the damage is positive and we have enough HP, subtract the damage from HP. 4. If the damage is positive and we don't have enough HP, use a potion if available. 5. Use a min-heap to keep track of the most damaging monsters and use potions on them as needed.

    Algorithm

    Here’s a step-by-step breakdown of the optimized algorithm:

    1. Initialize a min-heap to keep track of the most damaging monsters.
    2. Iterate through the list of monsters.
    3. If the damage is negative, add it to HP.
    4. If the damage is positive and we have enough HP, subtract the damage from HP and add the damage to the heap.
    5. If the damage is positive and we don't have enough HP, use a potion if available. Replace the smallest damage in the heap with the current damage if it is larger.
    6. Continue this process until all monsters are processed or HP drops to zero.

    Code Implementation

    import heapq
    
    def max_monsters_killed(K, HP, damage):
        # Min-heap to keep track of the most damaging monsters
        min_heap = []
        monsters_killed = 0
        
        for dmg in damage:
            if dmg < 0:
                # If damage is negative, increase HP
                HP += dmg
            else:
                if HP > dmg:
                    # If we have enough HP, fight the monster
                    HP -= dmg
                    heapq.heappush(min_heap, dmg)
                    monsters_killed += 1
                else:
                    if K > 0:
                        # Use a potion if available
                        K -= 1
                        monsters_killed += 1
                        if min_heap and min_heap[0] < dmg:
                            # Replace the smallest damage in the heap if current damage is larger
                            HP += heapq.heappop(min_heap)
                            HP -= dmg
                            heapq.heappush(min_heap, dmg)
                    else:
                        break
        
        return monsters_killed
    
    # Example usage
    K = 2
    HP = 10
    damage = [-3, 2, 3, -2, 8, 8, 6, 4, 3, 3]
    print(max_monsters_killed(K, HP, damage))  # Output: 8
    

    Complexity Analysis

    The time complexity of this approach is O(N log K), where N is the number of monsters and K is the number of potions. This is because we use a heap to keep track of the most damaging monsters, and heap operations (insert and remove) take O(log K) time.

    The space complexity is O(K) due to the heap storing up to K elements.

    Edge Cases

    Potential edge cases include:

    • All monsters have negative damage.
    • All monsters have damage greater than the initial HP.
    • HP is initially zero.
    • No potions are available (K = 0).

    Each of these cases should be tested to ensure the algorithm handles them correctly.

    Testing

    To test the solution comprehensively, consider the following test cases:

    • Simple cases with a few monsters and potions.
    • Cases with all negative damage values.
    • Cases with damage values greater than the initial HP.
    • Edge cases with zero HP or zero potions.

    Using a testing framework like unittest or pytest can help automate and validate these test cases.

    Thinking and Problem-Solving Tips

    When approaching such problems, consider the following tips:

    • Break down the problem into smaller, manageable parts.
    • Think about the optimal use of resources (e.g., potions) and how to maximize their effectiveness.
    • Use data structures like heaps to efficiently manage and prioritize elements.
    • Practice solving similar problems to improve your problem-solving skills.

    Conclusion

    In this blog post, we discussed how to solve the "Kill Monsters" problem using an optimized approach with a min-heap. We covered the problem definition, approach, algorithm, code implementation, complexity analysis, edge cases, and testing. Understanding and solving such problems is crucial for developing strong problem-solving skills and optimizing resource management in various scenarios.

    Additional Resources

    For further reading and practice, consider the following resources:

    Keep going with PythonAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free