Maximum Total Damage With Spell Casting
MedPrompt
A magician has various spells.
You are given an array power, where each element represents the damage of a spell. Multiple spells can have the same damage value.
It is a known fact that if a magician decides to cast a spell with a damage of power[i], they cannot cast any spell with a damage of power[i] - 2, power[i] - 1, power[i] + 1, or power[i] + 2.
Each spell can be cast only once.
Return the maximum possible total damage that a magician can cast.
Example 1:
Input: power = [1,1,3,4]
Output: 6
Explanation:
The maximum possible damage of 6 is produced by casting spells 0, 1, 3 with damage 1, 1, 4.
Example 2:
Input: power = [7,1,6,6]
Output: 13
Explanation:
The maximum possible damage of 13 is produced by casting spells 1, 2, 3 with damage 1, 6, 6.
Constraints:
1 <= power.length <= 1051 <= power[i] <= 109
Approaches
3 approaches with complexity analysis and trade-offs.
This approach explores all possible valid combinations of spells by making a decision for each unique spell power: either cast it or skip it. It uses a recursive function to traverse the decision tree. While simple to understand, its exponential nature makes it impractical for the given constraints.
Algorithm
- Create a frequency map of the
powerarray to count occurrences of each spell damage. - Extract the unique damage values into a list and sort them in ascending order. Let this be
uniquePowers. - Define a recursive function,
solve(index), which computes the maximum damage fromuniquePowersstarting fromindex. - Base Case: If
indexis out of bounds (>= uniquePowers.length), return 0. - Recursive Step: For the spell at
uniquePowers[index], there are two choices:- Skip: Don't cast this spell. The damage is
solve(index + 1). - Cast: Cast this spell. The damage is
(uniquePowers[index] * frequency). We then find the next non-conflicting spell. This is the first spelluniquePowers[nextIndex]such thatuniquePowers[nextIndex] > uniquePowers[index] + 2. The total damage for this choice is(current spell's damage) + solve(nextIndex).
- Skip: Don't cast this spell. The damage is
- The function returns the maximum of the 'Skip' and 'Cast' options.
- The initial call is
solve(0).
Walkthrough
The core idea is to simplify the problem first. Since the constraint depends on the damage value, not the spell's index, we can group spells by their power. If we decide to cast a spell with power p, it's always optimal to cast all spells with power p to maximize damage without adding new restrictions.
-
Preprocessing: We first count the frequencies of each power value using a HashMap. Then, we extract the unique power values and sort them. This gives us a sorted array of unique powers, say
uniquePowers. -
Recursion: We define a recursive function
solve(index)that calculates the maximum damage possible from the spells inuniquePowersfromindexto the end. At eachindex, we face a choice:- Skip
uniquePowers[index]: We move to the next unique power, so the damage issolve(index + 1). - Cast
uniquePowers[index]: We gainuniquePowers[index] * countdamage. Due to the constraint, we cannot cast spells with powerp+1orp+2. We must find the next available spell, which is the first one with power greater thanuniquePowers[index] + 2. Let its index benextIndex. We then add the result ofsolve(nextIndex).
- Skip
The function returns the maximum of these two outcomes. This method explores the entire search space, leading to a correct but very slow solution.
import java.util.*; class Solution { private Map<Integer, Integer> counts; private int[] uniquePowers; public long maximumTotalDamage(int[] power) { counts = new HashMap<>(); for (int p : power) { counts.put(p, counts.getOrDefault(p, 0) + 1); } uniquePowers = new int[counts.size()]; int i = 0; for (int p : counts.keySet()) { uniquePowers[i++] = p; } Arrays.sort(uniquePowers); return solve(0); } private long solve(int index) { if (index >= uniquePowers.length) { return 0; } // Option 1: Skip current power long damageSkip = solve(index + 1); // Option 2: Take current power long currentPower = uniquePowers[index]; long currentDamage = currentPower * counts.get((int)currentPower); // Find the next index to jump to int nextIndex = index + 1; while (nextIndex < uniquePowers.length && uniquePowers[nextIndex] <= currentPower + 2) { nextIndex++; } long damageTake = currentDamage + solve(nextIndex); return Math.max(damageSkip, damageTake); }}Complexity
Time
O(N + 2^M), where N is the length of the `power` array and M is the number of unique powers. O(N) is for preprocessing. The recursive part can have up to 2^M calls in the worst case, making it exponential.
Space
O(M), where M is the number of unique powers. This is for storing the unique powers and for the recursion stack depth.
Trade-offs
Pros
Conceptually simple and a direct translation of the problem statement.
Cons
Extremely inefficient due to its exponential time complexity.
Will result in a 'Time Limit Exceeded' (TLE) error for moderately large inputs.
Solutions
Solution
class Solution {private Long[] f;private int[] power;private Map<Integer, Integer> cnt;private int[] nxt;private int n;public long maximumTotalDamage(int[] power) { Arrays.sort(power); this.power = power; n = power.length; f = new Long[n]; cnt = new HashMap<>(n); nxt = new int[n]; for (int i = 0; i < n; ++i) { cnt.merge(power[i], 1, Integer : : sum); int l = Arrays.binarySearch(power, power[i] + 3); l = l < 0 ? -l - 1 : l; nxt[i] = l; } return dfs(0); }private long dfs(int i) { if (i >= n) { return 0; } if (f[i] != null) { return f[i]; } long a = dfs(i + cnt.get(power[i])); long b = 1L * power[i] * cnt.get(power[i]) + dfs(nxt[i]); return f[i] = Math.max(a, b); }}Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.