题目描述
给你一个整数数组 nums ,你可以对它进行一些操作。 每次操作中,选择任意一个 nums[i] ,删除它并获得 nums[i] 的点数。之后,你必须删除 所有 等于 nums[i] - 1 和 nums[i] + 1 的元素。
开始你拥有 0 个点数。返回你能通过这些操作获得的最大点数。
示例:
cpp
输入:nums = [2,2,3,3,3,4]
输出:9
//解释:删除 3 获得 3 个点数,接着要删除两个 2 和 4 。之后,再次删除 3 获得 3 个点数,再次删除 3 获得 3 个点数。总共获得 9 个点数。
解题思路
预处理
-
找到最大值 :首先,遍历
nums
数组找到其中的最大值n
。这是因为我们后续需要构建一个长度为n+1
的数组arr
来记录每个数字(从1到n)在nums
中出现的总次数乘以该数字的值。注意,arr[0]
始终为0,因为不存在数字0。 -
构建
arr
数组 :然后,再次遍历nums
数组,对于每个元素nums[i]
,将其值加到arr[nums[i]]
上。这样,arr[k]
就表示数字k
在nums
中出现的总次数乘以k
的值。
动态规划
动态规划的思路与打家劫舍中讲解的相似(动态规划-打家劫舍-CSDN博客)。
-
定义状态 :接下来,定义一个长度为
n+1
的数组dp
,其中dp[i]
表示在考虑前i
个数字(即数字1到i
)时,通过删除操作可以获得的最大点数。注意这里的"前i
个数字"是指arr
数组中索引为0到i
的元素,它们实际上对应了原数组nums
中的数字1到i
(如果它们存在的话)。 -
初始化:
dp[0]
初始化为arr[0]
,即0,因为没有数字可选时无法获得点数。dp[1]
初始化为max(arr[0], arr[1])
,但实际上由于arr[0]
总是0,所以这里就是arr[1]
,即数字1在nums
中出现的总次数乘以1的值(如果存在的话)。
-
状态转移:
- 对于
i > 1
的情况,我们需要考虑两种选择:- 不选择数字
i
,那么最大点数就是dp[i-1]
,即考虑前i-1
个数字时的最大点数。 - 选择数字
i
,那么由于不能选择相邻的数字,所以最大点数就是dp[i-2] + arr[i]
,即考虑前i-2
个数字时的最大点数加上数字i
在nums
中出现的总次数乘以i
的值。
- 不选择数字
- 因此,
dp[i]
的值就是这两种选择中的较大者,即dp[i] = max(dp[i-1], dp[i-2] + arr[i])
。
- 对于
结果
- 最后,遍历
dp
数组找到其中的最大值,即为通过删除操作可以获得的最大点数。
cpp
class Solution {
public:
int deleteAndEarn(vector<int>& nums) {
int n = 0;
for (int i = 0; i < nums.size(); i++) {
n = max(n, nums[i]);
}
vector<int> arr(n + 1, 0);
for (int i = 0; i < nums.size(); i++) {
arr[nums[i]] += nums[i];
}
vector<int> dp(n + 1, 0);
for (int i = 0; i <= n; i++) { // 0 0 2 3 4
if (i == 0) {
dp[i] = arr[i];
} else if (i == 1) {
dp[i] = max(arr[i], arr[i - 1]);
} else {
dp[i] = max(dp[i - 1], dp[i - 2] + arr[i]);
}
}
int ret = 0;
for (int i = 0; i <= n; i++) {
ret = max(ret, dp[i]);
}
return ret;
}
};