Taro Logo

Taking Maximum Energy From the Mystic Dungeon

Medium
Asked by:
Profile picture
Profile picture
Profile picture
3 views
Topics:
ArraysSliding WindowsGreedy Algorithms

In a mystic dungeon, n magicians are standing in a line. Each magician has an attribute that gives you energy. Some magicians can give you negative energy, which means taking energy from you.

You have been cursed in such a way that after absorbing energy from magician i, you will be instantly transported to magician (i + k). This process will be repeated until you reach the magician where (i + k) does not exist.

In other words, you will choose a starting point and then teleport with k jumps until you reach the end of the magicians' sequence, absorbing all the energy during the journey.

You are given an array energy and an integer k. Return the maximum possible energy you can gain.

Note that when you are reach a magician, you must take energy from them, whether it is negative or positive energy.

Example 1:

Input: energy = [5,2,-10,-5,1], k = 3

Output: 3

Explanation: We can gain a total energy of 3 by starting from magician 1 absorbing 2 + 1 = 3.

Example 2:

Input: energy = [-2,-3,-1], k = 2

Output: -1

Explanation: We can gain a total energy of -1 by starting from magician 2.

Constraints:

  • 1 <= energy.length <= 105
  • -1000 <= energy[i] <= 1000
  • 1 <= k <= energy.length - 1
​​​​​​

Solution


Clarifying Questions

When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:

  1. What are the dimensions of the dungeon (number of rooms and levels)? What are the maximum possible energy values in a room, and can energy values be negative?
  2. Is it always possible to reach the exit, or should I handle the case where no path exists to the exit that allows for a non-negative total energy?
  3. How should I handle cases where multiple paths yield the same maximum energy? Is any one of those paths acceptable, or is there a specific preference (e.g., shortest path)?
  4. Are the room energy values integers, or could they be floating-point numbers?
  5. What data structure represents the dungeon? Is it a 2D array, a graph represented with adjacency lists, or something else?

Brute Force Solution

Approach

The brute force approach to finding the maximum energy you can get from the mystic dungeon involves exploring every possible path. You try going through all possible room combinations, figuring out the energy you get from each. Then you simply pick the path that gives you the most energy.

Here's how the algorithm would work step-by-step:

  1. Start at the beginning of the dungeon.
  2. Consider all possible rooms you can enter next.
  3. For each of those next rooms, consider all possible rooms you can enter after that.
  4. Keep doing this until you reach an exit, or can't go to any further rooms.
  5. Calculate the total energy you gained for each complete path you explored.
  6. Compare the total energy from all the possible paths.
  7. Choose the path that gave you the highest total energy. This is the maximum energy you can get.

Code Implementation

def find_maximum_energy_brute_force(dungeon):
    maximum_energy_found = 0

    def explore_dungeon(current_room_index, current_energy):
        nonlocal maximum_energy_found

        # Add the energy from the current room to the total
        current_energy += dungeon[current_room_index]

        # If we're at the end, update the maximum energy
        if current_room_index == len(dungeon) - 1:
            maximum_energy_found = max(maximum_energy_found, current_energy)
            return

        # Explore all possible next rooms
        for next_room_index in range(current_room_index + 1, len(dungeon)):
            
            # Recursively explore from the next room
            explore_dungeon(next_room_index, current_energy)

    # Start exploring from the first room
    explore_dungeon(0, 0)

    return maximum_energy_found

Big(O) Analysis

Time Complexity
O(k^n)The brute force approach explores all possible paths in the dungeon. Let's say each room can have k possible exits, and the longest path has length n (where n is the number of rooms in the longest path). We are effectively generating a k-ary tree of paths where each level represents a decision on which room to enter next. Therefore, in the worst case, we explore approximately k^n paths. Calculating the energy for each path takes O(n) time, but the dominant factor is the number of paths, making the overall time complexity O(k^n).
Space Complexity
O(N)The brute force approach explores every possible path in the dungeon using recursion. In the worst-case scenario, the algorithm might explore a path that visits every room before reaching an exit or a dead end. The recursion depth can be at most N, where N is the number of rooms in the dungeon, leading to N stack frames. Each stack frame stores local variables and function parameters, contributing to O(N) auxiliary space for the recursion stack.

Optimal Solution

Approach

The best way to find the maximum energy is to figure out the best path through the dungeon one step at a time. We will use past choices to help make future decisions so we can always make the best move to gain the most energy.

Here's how the algorithm would work step-by-step:

  1. Start at the beginning of the dungeon.
  2. At each room, decide if you should go down or right.
  3. To make the decision, imagine you go both down and right. See what the maximum energy you could collect would be by taking each path.
  4. Pick the path that gives you the most energy.
  5. Remember the maximum energy you could gain from each room and direction.
  6. Repeat this process until you reach the end of the dungeon.
  7. Once at the end, trace back your steps to figure out exactly which path you took to get the maximum energy.

Code Implementation

def max_energy(dungeon):
    rows = len(dungeon)
    cols = len(dungeon[0])

    energy_collected = [[0] * cols for _ in range(rows)]
    path_taken = [[''] * cols for _ in range(rows)]

    energy_collected[0][0] = dungeon[0][0]

    for col_index in range(1, cols):
        energy_collected[0][col_index] = energy_collected[0][col_index - 1] + dungeon[0][col_index]
        path_taken[0][col_index] = 'R'

    for row_index in range(1, rows):
        energy_collected[row_index][0] = energy_collected[row_index - 1][0] + dungeon[row_index][0]
        path_taken[row_index][0] = 'D'

    for row_index in range(1, rows):
        for col_index in range(1, cols):
            # Determine the optimal path (down or right).
            if energy_collected[row_index - 1][col_index] > energy_collected[row_index][col_index - 1]:
                energy_collected[row_index][col_index] = energy_collected[row_index - 1][col_index] + dungeon[row_index][col_index]
                path_taken[row_index][col_index] = 'D'
            else:
                energy_collected[row_index][col_index] = energy_collected[row_index][col_index - 1] + dungeon[row_index][col_index]
                path_taken[row_index][col_index] = 'R'

    optimal_path = ''
    row_index = rows - 1
    col_index = cols - 1

    # Trace back to construct the optimal path.
    while row_index > 0 or col_index > 0:
        if path_taken[row_index][col_index] == 'D':
            optimal_path = 'D' + optimal_path
            row_index -= 1
        else:
            optimal_path = 'R' + optimal_path
            col_index -= 1

    return energy_collected[rows - 1][cols - 1], optimal_path

Big(O) Analysis

Time Complexity
O(m*n)The algorithm iterates through each room in the m x n dungeon exactly once. For each room, it calculates the maximum energy obtainable by going down and right, which takes constant time. Since each room is visited once, the overall time complexity is proportional to the number of rooms in the dungeon, resulting in a time complexity of O(m*n) where m is the number of rows and n is the number of columns.
Space Complexity
O(N*M)The algorithm remembers the maximum energy that can be gained from each room and direction. This requires storing energy values for each cell in the dungeon. If the dungeon has N rows and M columns, then we need an auxiliary data structure (likely a 2D array) of size N*M to store these intermediate maximum energy values. Additionally, tracing back the steps also requires storing path information for each room potentially requiring another N*M sized auxiliary data structure. Therefore, the space complexity is O(N*M), where N is the number of rows and M is the number of columns in the dungeon.

Edge Cases

CaseHow to Handle
Null or empty dungeon arrayReturn 0 immediately, as there's no energy to collect.
Dungeon array with a single room (one element)Return the value of the single room as the maximum energy.
Dungeon array with only negative energy roomsReturn the least negative (largest) value to minimize energy loss.
Dungeon array with all zero energy roomsReturn 0 as the maximum energy that can be collected.
Large dungeon array (performance scaling)Ensure the algorithm used has a time complexity that scales reasonably, like O(n) or O(n log n).
Integer overflow in energy calculationUse a data type that can accommodate large energy values (e.g., long long in C++, long in Java, or arbitrary precision integers in Python).
Dungeon array with extreme positive and negative valuesThe solution must handle potentially large differences between room values to avoid incorrect maximum energy calculations.
Array containing duplicate energy valuesThe algorithm should correctly consider all paths regardless of duplicate energy amounts available in different rooms.