题目大意
给你一个数组 power ,其中每个元素表示一个咒语的伤害值,可能会有多个咒语有相同的伤害值。
已知魔法师使用伤害值为 power[i] 的咒语时,他们就 不能 使用伤害为 power[i] - 2 ,power[i] - 1 ,power[i] + 1 或者 power[i] + 2 的咒语。
每个咒语最多只能被使用 一次 。
返回可以达到的伤害值之和的 最大值 。
数据范围
- 1 <= power.length <= 105
- 1 <= power[i] <= 109
思路
与 打家劫舍 相似
-
选择咒语与出现次序无关,只与值有关;并且一个咒语有多个可能值,是每个咒语最多只用一次,而 不是每个值的咒语最多只能用一次 , 所以可能将所有值进行统计,还有它们的出现次数,这样 值*出现次数=伤害值; 再将统计出来的进行排序, 因为选择值为x的就不能选x-1和x-2
-
从后往前推(dfs),最后一个是i
- 选i ,那么i - 1 和 i - 2 都不能选,只能另外选择最大的小于i - 2的数
- 难点在于如果像之前一样开个 vector
f(ma + 1)的数组,或者使用a, b, c, d依次更新,由于数据过大,会导致爆内存 - 就要使用哈希表,以及用while循环来寻找 最大的小于i-2的数,并且在power中出现了
- 难点在于如果像之前一样开个 vector
- 不选i,那么就选i前面的一个数即可。这里的前面的一个数,并不是\(i-1\),而是在power中的数排序之后,排在i前面的一个数
- 选i ,那么i - 1 和 i - 2 都不能选,只能另外选择最大的小于i - 2的数
-
从前往后推(递推),当前是i
-
选i,再选择<i - 2最大的数,也是用while循环
-
不选i,直接选择i的前一个数
-
比较这两种情况谁更大
-
代码
爆内存的普通做法
class Solution {
public:using ll = long long;long long maximumTotalDamage(vector<int>& power) {int ma = ranges::max(power);int n = power.size(), cnt = 0;vector<int> a(ma + 1);for(auto& u : power) {if(a[u] == 0) cnt++;a[u] += u;}vector<ll> f(ma + 1);if(cnt == 1) return a[ma];f[1] = a[1], f[2] = max(a[1], a[2]);for(int i = 3; i <= ma; i++) {f[i] = max(f[i - 1], f[i - 2]);f[i] = max(f[i], f[i - 3] + a[i]);}return f[ma];}
};
递归版改进做法
class Solution {
public:using ll = long long;long long maximumTotalDamage(vector<int>& power) {unordered_map<ll, ll> mp;for(auto& u : power) mp[u]++;vector<pair<ll, ll>> a(mp.begin(), mp.end());ranges::sort(a);ll n = a.size();vector<ll> memo(n, -1);auto dfs = [&](auto&& dfs, ll x) -> ll {if(x < 0) return 0;ll& res = memo[x];if(res != -1) return res;ll j = x;auto& [u, v] = a[x];while(j && a[j - 1].first >= a[x].first - 2) j--;return res = max(dfs(dfs, x - 1), dfs(dfs, j - 1) + (ll)u * v);};return dfs(dfs, n - 1);}
};
递推版改进做法
class Solution {
public:using ll = long long;long long maximumTotalDamage(vector<int>& power) {unordered_map<ll, ll> mp;for(auto& u : power) mp[u] ++;vector<pair<ll, ll>> a(mp.begin(), mp.end());ranges::sort(a); //[出现的数, 出现次数]ll n = a.size();vector<ll> f(n); //第几小的值的出现次数f[0] = (ll)a[0].first * a[0].second; //最小的值*出现次数for(int i = 1, j = 0; i < n; i++) {auto& [u, v] = a[i];while(u - a[j].first > 2) j++; //直到u - a[j].first <= 2 !!!那么u - a[j - 1].first肯定会 < 2 !符合if(j == 0) f[i] = max(f[i - 1], (ll)u * v); //j不能减少了else f[i] = max(f[i - 1], f[j - 1] + (ll)u * v);}return f[n - 1];}
};