3147. Taking Maximum Energy From the Mystic Dungeon
Problem Description
You are in a mystic dungeon with n
magicians standing in a line. Each magician has an energy value that can be positive (giving you energy) or negative (taking energy from you).
You've been cursed with a teleportation spell: after absorbing energy from magician at position i
, you automatically teleport to magician at position (i + k)
. This continues until you reach a position where (i + k)
doesn't exist (goes beyond the array).
Your journey works as follows:
- Choose any starting magician position
- Absorb that magician's energy (you must take it, whether positive or negative)
- Teleport forward by exactly
k
positions - Repeat until you can't teleport anymore (would go out of bounds)
Given an array energy
representing each magician's energy value and an integer k
representing the teleport distance, find the maximum total energy you can gain by choosing the optimal starting position.
For example, if energy = [5, -3, 2, -1, 4]
and k = 2
:
- Starting at index 0: you'd visit positions 0 → 2 → 4, gaining 5 + 2 + 4 = 11 energy
- Starting at index 1: you'd visit positions 1 → 3, gaining -3 + (-1) = -4 energy
- Starting at index 2: you'd visit position 2 only, gaining 2 energy
- Starting at index 3: you'd visit position 3 only, gaining -1 energy
- Starting at index 4: you'd visit position 4 only, gaining 4 energy
The maximum energy would be 11 by starting at index 0.
Intuition
The key insight is recognizing that magicians form groups based on the jump distance k
. When we jump by k
positions each time, we're essentially visiting every k
-th element starting from our initial position.
Think about it this way: if we start at position i
, we visit positions i
, i+k
, i+2k
, i+3k
, and so on until we go out of bounds. This means the array is divided into k
independent chains, where:
- Chain 0 contains positions: 0, k, 2k, 3k, ...
- Chain 1 contains positions: 1, k+1, 2k+1, 3k+1, ...
- Chain 2 contains positions: 2, k+2, 2k+2, 3k+2, ...
- And so on...
Each starting position belongs to exactly one chain, and once we start on a chain, we must follow it to the end. The crucial observation is that we want to maximize our energy gain, which means we might not want to start at the beginning of a chain - we might want to skip some negative values at the start and begin somewhere in the middle.
Since we must move forward by k
each time, the last possible positions we can visit are in the range [n-k, n-1]
. These are the potential ending positions of our journey. Working backwards from these endpoints, we can calculate the sum of energy values at each k
interval.
By iterating through each possible endpoint and traversing backwards (subtracting k
each time), we can compute all possible suffix sums for each chain. As we build these suffix sums, we keep track of the maximum sum encountered. This approach ensures we find the optimal starting position that gives us the maximum energy, as it considers all possible sub-paths within each chain.
The backwards traversal is elegant because it naturally handles the constraint that we must take all energy values from our starting point to the end - we're essentially asking "what's the best suffix of each chain to take?"
Learn more about Prefix Sum patterns.
Solution Approach
The implementation uses the enumeration and suffix sum strategy to find the maximum energy gain.
We start by initializing ans = -inf
to track the maximum energy found so far, and get the array length n
.
The core of the algorithm is a loop that iterates through all possible ending positions:
for i in range(n - k, n):
This range [n-k, n)
represents the last k
positions in the array. These are the only positions that can serve as endpoints of our journey since any position before n-k
would have at least one more jump of size k
that stays within bounds.
For each ending position i
, we traverse backwards through its chain:
j, s = i, 0
while j >= 0:
s += energy[j]
ans = max(ans, s)
j -= k
Here's what happens in this inner loop:
j
starts at the ending positioni
and moves backwards byk
each iterations
accumulates the energy sum as we move backwards through the chain- After adding each energy value to
s
, we updateans
with the maximum between currentans
ands
The key insight is that by building the sum incrementally while moving backwards, we're calculating all possible suffix sums for this chain. For example, if we're traversing positions [2, 7, 12]
backwards (starting from position 12), we calculate:
- Sum from position 12 only:
energy[12]
- Sum from positions 7 to 12:
energy[12] + energy[7]
- Sum from positions 2 to 12:
energy[12] + energy[7] + energy[2]
By updating ans
after each addition, we capture the maximum energy achievable by starting at any position within this chain.
The algorithm effectively checks all k
chains (one for each ending position in [n-k, n)
), and for each chain, it evaluates all possible starting positions by computing suffix sums. The time complexity is O(n)
since each element is visited at most once across all chains, and the space complexity is O(1)
as we only use a few variables to track the current state.
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 concrete example with energy = [3, -2, 5, -1, 4, -3, 2]
and k = 3
.
First, we identify our possible ending positions. Since n = 7
and k = 3
, the ending positions are in range [n-k, n)
= [4, 7)
, which means indices 4, 5, and 6.
Processing ending position 4 (index 4):
- Start at position 4:
s = 0 + energy[4] = 0 + 4 = 4
, updateans = 4
- Jump back to position 1:
s = 4 + energy[1] = 4 + (-2) = 2
, updateans = max(4, 2) = 4
- Jump back to position -2: Out of bounds, stop
Chain visited: [1, 4] with suffix sums: [4, 2]
Processing ending position 5 (index 5):
- Start at position 5:
s = 0 + energy[5] = 0 + (-3) = -3
, updateans = max(4, -3) = 4
- Jump back to position 2:
s = -3 + energy[2] = -3 + 5 = 2
, updateans = max(4, 2) = 4
- Jump back to position -1: Out of bounds, stop
Chain visited: [2, 5] with suffix sums: [-3, 2]
Processing ending position 6 (index 6):
- Start at position 6:
s = 0 + energy[6] = 0 + 2 = 2
, updateans = max(4, 2) = 4
- Jump back to position 3:
s = 2 + energy[3] = 2 + (-1) = 1
, updateans = max(4, 1) = 4
- Jump back to position 0:
s = 1 + energy[0] = 1 + 3 = 4
, updateans = max(4, 4) = 4
- Jump back to position -3: Out of bounds, stop
Chain visited: [0, 3, 6] with suffix sums: [2, 1, 4]
The maximum energy we can gain is 4, which can be achieved by:
- Starting at index 4 and taking only that magician's energy (4)
- OR starting at index 0 and visiting positions 0→3→6, gaining 3 + (-1) + 2 = 4
The algorithm correctly identifies both optimal paths by computing all possible suffix sums across the chains.
Solution Implementation
1from typing import List
2from math import inf
3
4class Solution:
5 def maximumEnergy(self, energy: List[int], k: int) -> int:
6 # Initialize the maximum energy to negative infinity
7 max_energy = -inf
8 n = len(energy)
9
10 # Try each of the last k positions as potential starting points
11 # These are the only positions that can be reached by jumping backwards by k
12 for start_pos in range(n - k, n):
13 current_pos = start_pos
14 current_sum = 0
15
16 # Jump backwards by k steps from the starting position
17 # Accumulate the energy values along the path
18 while current_pos >= 0:
19 current_sum += energy[current_pos]
20 # Update the maximum energy if current sum is larger
21 max_energy = max(max_energy, current_sum)
22 # Move to the previous position by jumping k steps back
23 current_pos -= k
24
25 return max_energy
26
1class Solution {
2 public int maximumEnergy(int[] energy, int k) {
3 // Initialize the maximum energy to a very small value (negative large number)
4 int maxEnergy = -(1 << 30); // -2^30
5 int n = energy.length;
6
7 // Iterate through all possible ending positions
8 // Starting from the last k positions (n-k to n-1)
9 for (int endPosition = n - k; endPosition < n; endPosition++) {
10 // For each ending position, traverse backwards with step k
11 // and calculate the cumulative sum
12 int currentSum = 0;
13 for (int currentIndex = endPosition; currentIndex >= 0; currentIndex -= k) {
14 // Add the energy at current position to the running sum
15 currentSum += energy[currentIndex];
16 // Update the maximum energy if current sum is larger
17 maxEnergy = Math.max(maxEnergy, currentSum);
18 }
19 }
20
21 return maxEnergy;
22 }
23}
24
1class Solution {
2public:
3 int maximumEnergy(vector<int>& energy, int k) {
4 // Initialize the maximum energy to a very small value (negative infinity)
5 int maxEnergy = -(1 << 30);
6 int n = energy.size();
7
8 // Iterate through all possible ending positions (last k positions)
9 // These are the potential starting points for our backward traversal
10 for (int endPos = n - k; endPos < n; ++endPos) {
11 // For each ending position, traverse backwards with step k
12 // and calculate the cumulative sum
13 int currentSum = 0;
14 for (int currentPos = endPos; currentPos >= 0; currentPos -= k) {
15 // Add the energy at current position to the running sum
16 currentSum += energy[currentPos];
17 // Update the maximum energy if current sum is greater
18 maxEnergy = max(maxEnergy, currentSum);
19 }
20 }
21
22 return maxEnergy;
23 }
24};
25
1/**
2 * Calculates the maximum energy that can be obtained by jumping through the array
3 * @param energy - Array of energy values at each position
4 * @param k - The fixed jump distance
5 * @returns The maximum energy sum that can be collected
6 */
7function maximumEnergy(energy: number[], k: number): number {
8 const arrayLength: number = energy.length;
9 let maxEnergy: number = -Infinity;
10
11 // Start from the last k positions (possible ending positions)
12 for (let startIndex: number = arrayLength - k; startIndex < arrayLength; startIndex++) {
13 // For each starting position, jump backwards by k steps and accumulate energy
14 let currentSum: number = 0;
15 for (let currentIndex: number = startIndex; currentIndex >= 0; currentIndex -= k) {
16 currentSum += energy[currentIndex];
17 // Update maximum energy if current sum is greater
18 maxEnergy = Math.max(maxEnergy, currentSum);
19 }
20 }
21
22 return maxEnergy;
23}
24
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the array energy
.
The algorithm iterates through k
starting positions (from index n-k
to n-1
). For each starting position i
, it traverses backwards with step size k
until reaching the beginning of the array. The key insight is that each element in the array is visited at most once across all iterations. This is because each element belongs to exactly one "chain" when jumping backwards by k
positions, and we only process each chain once by starting from its last possible position in the range [n-k, n-1]
. Therefore, the total number of operations is proportional to n
.
The space complexity is O(1)
. The algorithm only uses a constant amount of extra space for variables ans
, i
, j
, and s
, regardless of the input size.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Misunderstanding the Direction of Traversal
Pitfall: A common mistake is thinking we need to traverse forward from each starting position to find the maximum energy. This leads to inefficient code that checks every possible starting position:
# Inefficient approach - checking all starting positions
def maximumEnergy(energy: List[int], k: int) -> int:
max_energy = -inf
n = len(energy)
# Wrong: Trying every position as a starting point
for start in range(n):
current_sum = 0
pos = start
while pos < n:
current_sum += energy[pos]
pos += k
max_energy = max(max_energy, current_sum)
return max_energy
Why it's wrong: This approach has O(n²/k) time complexity because we're checking all n starting positions, when we only need to check k distinct chains.
Solution: Recognize that positions form k independent chains based on their modulo k value. By starting from the end positions and working backwards, we can efficiently compute all possible sums in O(n) time.
2. Not Updating Maximum After Each Addition
Pitfall: Only checking the total sum at the end of each chain traversal:
# Wrong: Only checking complete chain sums
def maximumEnergy(energy: List[int], k: int) -> int:
max_energy = -inf
n = len(energy)
for i in range(n - k, n):
j, s = i, 0
while j >= 0:
s += energy[j]
j -= k
# Wrong: Only updating after the complete chain
max_energy = max(max_energy, s)
return max_energy
Why it's wrong: This misses optimal solutions where starting from the middle of a chain gives better results. For example, with energy = [-5, 10, -3]
and k = 2
, starting at index 2 gives -3 (better than starting at index 0 which gives -5 + (-3) = -8).
Solution: Update the maximum after each energy addition to capture all possible suffix sums:
while j >= 0:
s += energy[j]
max_energy = max(max_energy, s) # Update after EACH addition
j -= k
3. Incorrect Range for Ending Positions
Pitfall: Using the wrong range for potential ending positions:
# Wrong: Incorrect range
for i in range(n - k + 1, n + 1): # Off by one error
# ...
or
# Wrong: Starting from n - k - 1
for i in range(n - k - 1, n): # Includes positions that aren't endpoints
# ...
Why it's wrong: The correct ending positions are exactly the last k positions: indices [n-k, n-k+1, ..., n-1]
. Any position before n-k
would have another position k
steps ahead within the array bounds.
Solution: Use the precise range range(n - k, n)
which gives us exactly the last k positions of the array.
4. Forgetting Edge Cases
Pitfall: Not handling cases where k equals or exceeds n:
# Potential issue when k >= n
for i in range(n - k, n): # Could result in negative start of range
# ...
Why it's problematic: When k >= n
, every position becomes its own independent chain (no jumps possible), and range(n - k, n)
might not cover all positions correctly.
Solution: Add validation or ensure the algorithm handles this naturally:
def maximumEnergy(energy: List[int], k: int) -> int:
max_energy = -inf
n = len(energy)
# When k >= n, each position is its own chain
start_range = max(0, n - k)
for i in range(start_range, n):
j, s = i, 0
while j >= 0:
s += energy[j]
max_energy = max(max_energy, s)
j -= k
return max_energy
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
Prefix Sum The prefix sum is an incredibly powerful and straightforward technique Its primary goal is to allow for constant time range sum queries on an array What is Prefix Sum The prefix sum of an array at index i is the sum of all numbers from index 0 to i By
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!