【LetMeFly】3186.施咒的最大总伤害:动态规划+双指针——O(1)空间(暂未发现其他O(1)空间的题解)
力扣题目链接:https://leetcode.cn/problems/maximum-total-damage-with-spell-casting/
一个魔法师有许多不同的咒语。
给你一个数组 power ,其中每个元素表示一个咒语的伤害值,可能会有多个咒语有相同的伤害值。
已知魔法师使用伤害值为 power[i] 的咒语时,他们就 不能 使用伤害为 power[i] - 2 ,power[i] - 1 ,power[i] + 1 或者 power[i] + 2 的咒语。
每个咒语最多只能被使用 一次 。
请你返回这个魔法师可以达到的伤害值之和的 最大值 。
 
示例 1:
输入:power = [1,1,3,4]
输出:6
解释:
可以使用咒语 0,1,3,伤害值分别为 1,1,4,总伤害值为 6 。
 
示例 2:
输入:power = [7,1,6,6]
输出:13
解释:
可以使用咒语 1,2,3,伤害值分别为 1,6,6,总伤害值为 13 。
 
 
提示:
	1 <= power.length <= 105 
	1 <= power[i] <= 109 
    
解题方法一:动态规划(O(n)空间)
首先二话不说对power数组来个哈希表统计和排序,得到这样的数组:[<1出现2次>, <3出现1次>, ...]。排序依据是power小的优先。
创建动态规划数组,dp[i+1]表示power[0]到power[i]所能达到的最大总伤害(dp[0]=0,此处power[i]代表排序后)。
这样就有状态转移方程:
$dp[i+1]=\max(dp[i], dp[j]+thisVal)$
其中$dp[i]$代表不使用当前power,$dp[j]$是最后一个满足$\lt power[i]-2$的power,$thisVal$代表使用这个power产生的总能量($thisVal=power[i]\times times[i]$)。
现在就剩下最后一个问题了,如何快速得到小于$power[i]-2$的最大power是谁,也就是如何得到j的大小。
双指针即可。每当处理一个新$power[i]$时,当$power[j]\lt power[i]-2$时不断另$j+=1$即可。
- 时间复杂度$O(n\log n)$,其中$n=len(power)$,时间复杂度的来源主要是排序
 
- 空间复杂度$O(n)$,空间复杂度的来源主要是动态规划数组
 
AC代码
C++
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
   | 
 
  typedef long long ll; class Solution { public:     ll maximumTotalDamage(vector<int>& power) {         unordered_map<int, int> cnt;         for (int t : power) {             cnt[t]++;         }         vector<pair<int, int>> values(cnt.begin(), cnt.end());         sort(values.begin(), values.end());         vector<ll> dp(values.size() + 1);         for (int i = 0, j = 0; i < values.size(); i++) {             auto& [value, times] = values[i];             while (values[j].first < value - 2) {                 j++;             }             dp[i + 1] = max(dp[i], dp[j] + (ll) value * times);         }         return dp.back();     } };
 
 
  | 
 
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
   | ''' Author: LetMeFly Date: 2025-10-11 18:10:29 LastEditors: LetMeFly.xyz LastEditTime: 2025-10-11 18:46:52 ''' from typing import List from collections import Counter
  class Solution:     def maximumTotalDamage(self, power: List[int]) -> int:         cnt = Counter(power)         values = sorted(cnt)         dp = [0] * (len(values) + 1)         j = 0         for i, val in enumerate(values):             while values[j] < val - 2:                 j += 1             dp[i + 1] = max(dp[i], dp[j] + val * cnt[val])         return dp[-1]
 
  | 
 
解题方法二:动态规划(O(1)空间)
不难发现方法一中空间复杂度的来源主要是动态规划数组,并且不难发现动态规划数组只需要利用到最近几个。
这是因为和power[i]冲突的power范围向前看也就有个$power[i]-1$和$power[i]-2$,假设power中有这两个值,那么至多也就会用到两个前面的dp值。
所以可以使用数个变量来完成dp数组的原地滚动。
- 时间复杂度$O(n\log n)$,其中$n=len(power)$,时间复杂度的来源主要是排序
 
- 空间复杂度$O(1)$,相比于方法一,原地滚动大大简化了动态规划数组所需的空间
 
AC代码
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
   | ''' Author: LetMeFly Date: 2025-10-11 18:10:29 LastEditors: LetMeFly.xyz LastEditTime: 2025-10-11 19:04:17 ''' from typing import List from collections import Counter
  class Solution:     def maximumTotalDamage(self, power: List[int]) -> int:         cnt = Counter(power)         values = sorted(cnt)         dp0 = dp1 = dp2 = 0         val1 = val2 = -10         for val in values:             valSum = val * cnt[val]             if val - val2 > 2:                   useThis = valSum + dp2             elif val - val1 > 2:                   useThis = valSum + dp1             else:                   useThis = valSum + dp0             dp = max(useThis, dp2)             dp0, dp1, dp2 = dp1, dp2, dp             val1, val2 = val2, val         return dp2
 
  | 
 
同步发文于CSDN和我的个人博客,原创不易,转载经作者同意后请附上原文链接哦~
千篇源码题解已开源