|
| 1 | +--- |
| 2 | +title: 3186.施咒的最大总伤害:动态规划+双指针——O(1)空间(暂未发现其他O(1)空间的题解) |
| 3 | +date: 2025-10-11 19:05:05 |
| 4 | +tags: [题解, LeetCode, 中等, 数组, 哈希表, map, 双指针, 二分查找, 动态规划, DP, 计数, 排序] |
| 5 | +categories: [题解, LeetCode] |
| 6 | +--- |
| 7 | + |
| 8 | +# 【LetMeFly】3186.施咒的最大总伤害:动态规划+双指针——O(1)空间(暂未发现其他O(1)空间的题解) |
| 9 | + |
| 10 | +力扣题目链接:[https://leetcode.cn/problems/maximum-total-damage-with-spell-casting/](https://leetcode.cn/problems/maximum-total-damage-with-spell-casting/) |
| 11 | + |
| 12 | +<p>一个魔法师有许多不同的咒语。</p> |
| 13 | + |
| 14 | +<p>给你一个数组 <code>power</code> ,其中每个元素表示一个咒语的伤害值,可能会有多个咒语有相同的伤害值。</p> |
| 15 | + |
| 16 | +<p>已知魔法师使用伤害值为 <code>power[i]</code> 的咒语时,他们就 <strong>不能</strong> 使用伤害为 <code>power[i] - 2</code> ,<code>power[i] - 1</code> ,<code>power[i] + 1</code> 或者 <code>power[i] + 2</code> 的咒语。</p> |
| 17 | + |
| 18 | +<p>每个咒语最多只能被使用 <strong>一次</strong> 。</p> |
| 19 | + |
| 20 | +<p>请你返回这个魔法师可以达到的伤害值之和的 <strong>最大值</strong> 。</p> |
| 21 | + |
| 22 | +<p> </p> |
| 23 | + |
| 24 | +<p><strong class="example">示例 1:</strong></p> |
| 25 | + |
| 26 | +<div class="example-block"> |
| 27 | +<p><span class="example-io"><b>输入:</b>power = [1,1,3,4]</span></p> |
| 28 | + |
| 29 | +<p><span class="example-io"><b>输出:</b>6</span></p> |
| 30 | + |
| 31 | +<p><strong>解释:</strong></p> |
| 32 | + |
| 33 | +<p>可以使用咒语 0,1,3,伤害值分别为 1,1,4,总伤害值为 6 。</p> |
| 34 | +</div> |
| 35 | + |
| 36 | +<p><strong class="example">示例 2:</strong></p> |
| 37 | + |
| 38 | +<div class="example-block"> |
| 39 | +<p><span class="example-io"><b>输入:</b>power = [7,1,6,6]</span></p> |
| 40 | + |
| 41 | +<p><span class="example-io"><b>输出:</b>13</span></p> |
| 42 | + |
| 43 | +<p><strong>解释:</strong></p> |
| 44 | + |
| 45 | +<p>可以使用咒语 1,2,3,伤害值分别为 1,6,6,总伤害值为 13 。</p> |
| 46 | +</div> |
| 47 | + |
| 48 | +<p> </p> |
| 49 | + |
| 50 | +<p><strong>提示:</strong></p> |
| 51 | + |
| 52 | +<ul> |
| 53 | + <li><code>1 <= power.length <= 10<sup>5</sup></code></li> |
| 54 | + <li><code>1 <= power[i] <= 10<sup>9</sup></code></li> |
| 55 | +</ul> |
| 56 | + |
| 57 | + |
| 58 | + |
| 59 | +## 解题方法一:动态规划(O(n)空间) |
| 60 | + |
| 61 | +首先二话不说对power数组来个哈希表统计和排序,得到这样的数组:`[<1出现2次>, <3出现1次>, ...]`。排序依据是power小的优先。 |
| 62 | + |
| 63 | +创建动态规划数组,dp[i+1]表示power[0]到power[i]所能达到的最大总伤害(dp[0]=0,此处power[i]代表排序后)。 |
| 64 | + |
| 65 | +这样就有状态转移方程: |
| 66 | + |
| 67 | +$dp[i+1]=\max(dp[i], dp[j]+thisVal)$ |
| 68 | + |
| 69 | +其中$dp[i]$代表不使用当前power,$dp[j]$是最后一个满足$\lt power[i]-2$的power,$thisVal$代表使用这个power产生的总能量($thisVal=power[i]\times times[i]$)。 |
| 70 | + |
| 71 | +现在就剩下最后一个问题了,如何快速得到小于$power[i]-2$的最大power是谁,也就是如何得到j的大小。 |
| 72 | + |
| 73 | +双指针即可。每当处理一个新$power[i]$时,当$power[j]\lt power[i]-2$时不断另$j+=1$即可。 |
| 74 | + |
| 75 | ++ 时间复杂度$O(n\log n)$,其中$n=len(power)$,时间复杂度的来源主要是排序 |
| 76 | ++ 空间复杂度$O(n)$,空间复杂度的来源主要是动态规划数组 |
| 77 | + |
| 78 | +### AC代码 |
| 79 | + |
| 80 | +#### C++ |
| 81 | + |
| 82 | +```cpp |
| 83 | +/* |
| 84 | + * @LastEditTime: 2025-10-11 18:38:56 |
| 85 | + */ |
| 86 | +typedef long long ll; |
| 87 | +class Solution { |
| 88 | +public: |
| 89 | + ll maximumTotalDamage(vector<int>& power) { |
| 90 | + unordered_map<int, int> cnt; |
| 91 | + for (int t : power) { |
| 92 | + cnt[t]++; |
| 93 | + } |
| 94 | + vector<pair<int, int>> values(cnt.begin(), cnt.end()); |
| 95 | + sort(values.begin(), values.end()); |
| 96 | + vector<ll> dp(values.size() + 1); |
| 97 | + for (int i = 0, j = 0; i < values.size(); i++) { |
| 98 | + auto& [value, times] = values[i]; |
| 99 | + while (values[j].first < value - 2) { |
| 100 | + j++; |
| 101 | + } |
| 102 | + dp[i + 1] = max(dp[i], dp[j] + (ll) value * times); |
| 103 | + } |
| 104 | + return dp.back(); |
| 105 | + } |
| 106 | +}; |
| 107 | + |
| 108 | +``` |
| 109 | +
|
| 110 | +
|
| 111 | +
|
| 112 | +#### Python |
| 113 | +
|
| 114 | +```python |
| 115 | +''' |
| 116 | +Author: LetMeFly |
| 117 | +Date: 2025-10-11 18:10:29 |
| 118 | +LastEditors: LetMeFly.xyz |
| 119 | +LastEditTime: 2025-10-11 18:46:52 |
| 120 | +''' |
| 121 | +from typing import List |
| 122 | +from collections import Counter |
| 123 | +
|
| 124 | +class Solution: |
| 125 | + def maximumTotalDamage(self, power: List[int]) -> int: |
| 126 | + cnt = Counter(power) |
| 127 | + values = sorted(cnt) |
| 128 | + dp = [0] * (len(values) + 1) |
| 129 | + j = 0 |
| 130 | + for i, val in enumerate(values): |
| 131 | + while values[j] < val - 2: |
| 132 | + j += 1 |
| 133 | + dp[i + 1] = max(dp[i], dp[j] + val * cnt[val]) |
| 134 | + return dp[-1] |
| 135 | +``` |
| 136 | + |
| 137 | +## 解题方法二:动态规划(O(1)空间) |
| 138 | + |
| 139 | +不难发现方法一中空间复杂度的来源主要是动态规划数组,并且不难发现动态规划数组只需要利用到最近几个。 |
| 140 | + |
| 141 | +这是因为和power[i]冲突的power范围向前看也就有个$power[i]-1$和$power[i]-2$,假设power中有这两个值,那么至多也就会用到两个前面的dp值。 |
| 142 | + |
| 143 | +所以可以使用数个变量来完成dp数组的原地滚动。 |
| 144 | + |
| 145 | ++ 时间复杂度$O(n\log n)$,其中$n=len(power)$,时间复杂度的来源主要是排序 |
| 146 | ++ 空间复杂度$O(1)$,相比于方法一,原地滚动大大简化了动态规划数组所需的空间 |
| 147 | + |
| 148 | +### AC代码 |
| 149 | + |
| 150 | +#### Python |
| 151 | + |
| 152 | +```python |
| 153 | +''' |
| 154 | +Author: LetMeFly |
| 155 | +Date: 2025-10-11 18:10:29 |
| 156 | +LastEditors: LetMeFly.xyz |
| 157 | +LastEditTime: 2025-10-11 19:04:17 |
| 158 | +''' |
| 159 | +from typing import List |
| 160 | +from collections import Counter |
| 161 | + |
| 162 | +class Solution: |
| 163 | + def maximumTotalDamage(self, power: List[int]) -> int: |
| 164 | + cnt = Counter(power) |
| 165 | + values = sorted(cnt) |
| 166 | + dp0 = dp1 = dp2 = 0 |
| 167 | + val1 = val2 = -10 |
| 168 | + for val in values: |
| 169 | + valSum = val * cnt[val] |
| 170 | + if val - val2 > 2: # 无冲突 |
| 171 | + useThis = valSum + dp2 |
| 172 | + elif val - val1 > 2: # 和val2相差小于2,但和val1不冲突 |
| 173 | + useThis = valSum + dp1 |
| 174 | + else: # 和val1、val2都冲突(一定不会再和前面的冲突了) |
| 175 | + useThis = valSum + dp0 |
| 176 | + dp = max(useThis, dp2) |
| 177 | + dp0, dp1, dp2 = dp1, dp2, dp |
| 178 | + val1, val2 = val2, val |
| 179 | + return dp2 |
| 180 | +``` |
| 181 | + |
| 182 | +> 同步发文于[CSDN](https://letmefly.blog.csdn.net/article/details/153067130)和我的[个人博客](https://blog.letmefly.xyz/),原创不易,转载经作者同意后请附上[原文链接](https://blog.letmefly.xyz/2025/10/11/LeetCode%203186.%E6%96%BD%E5%92%92%E7%9A%84%E6%9C%80%E5%A4%A7%E6%80%BB%E4%BC%A4%E5%AE%B3/)哦~ |
| 183 | +> |
| 184 | +> 千篇源码题解[已开源](https://github.com/LetMeFly666/LeetCode) |
0 commit comments