动态规划-简单多状态dp问题 – 删除并获得点数
文章目录
- 动态规划-简单多状态dp问题 -- 删除并获得点数
- 题目重现
- 读懂题目
- 算法流程
- 示例代码
题目重现
题目链接:删除并获得点数 - 力扣
给你一个整数数组
nums
,你可以对它进行一些操作。每次操作中,选择任意一个
nums[i]
,删除它并获得nums[i]
的点数。之后,你必须删除 所有 等于nums[i] - 1
和nums[i] + 1
的元素。开始你拥有
0
个点数。返回你能通过这些操作获得的最大点数。
示例 1:
输入:nums = [3,4,2] 输出:6 解释: 删除 4 获得 4 个点数,因此 3 也被删除。 之后,删除 2 获得 2 个点数。总共获得 6 个点数。
示例 2:
输入:nums = [2,2,3,3,3,4] 输出:9 解释: 删除 3 获得 3 个点数,接着要删除两个 2 和 4 。 之后,再次删除 3 获得 3 个点数,再次删除 3 获得 3 个点数。 总共获得 9 个点数。
提示:
1 <= nums.length <= 2 * 104
1 <= nums[i] <= 104
读懂题目
根据题目描述,选择 x 数字的时候, x - 1 与 x + 1 是不能被选择的。像「打家劫舍」问题中,选择 i 位置的金额之后,就不能选择 i - 1 位置以及 i + 1 位置的金额。因此,我们可以创建一个大小为 10001 (根据题目的数据范围)的 hash 数组,将 nums 数组中每一个元素 x ,累加到 hash 数组下标为 x 的位置处,然后在 hash 数组上来一次「打家劫舍」即可。
算法流程
1.状态表示
用 dp[i] 表示以 i 位置为结尾对应的最大点数,但是考虑到对于每个 i 位置都存在两种情况:选择与不被选择。所以我们将 dp[i] 拆分为两种状态表示,不妨表示为 f[i]、g[i] ,其含义分别为:
f[i]:选择 i 位置,以 i 位置为结尾对应的最大点数
g[i]:选择 i 位置,以 i 位置为结尾对应的最大点数
2.状态转移方程
hash 表我们将其数组名定义为:num_countadd_set,根据 “读懂题目” 部分的内容,我们需要明确 num_countadd_set 数组的下标对应的是题中所给 nums 数组元素值。
比如:数字2580在 nums 数组中出现了 4 次,那么 num_count_set 就应该在下标为 2580 处的值为 2580*4 = 10320 。
这样设计的初衷是为了对应题目要求:选择一个 nums[i]
,删除它并获得 nums[i]
的点数并删除 所有 等于 nums[i] - 1
和 nums[i] + 1
的元素。如此设计就可以避免选择了点数为 nums[i] 的位置后还会选择到点数为 nums[i] + 1 或 nums[i] - 1 的位置。
于此,根据常规的打家劫舍问题,就能得到状态转移方程:
- f[i] = g[i - 1] + num_countadd_set[i]
- g[i] = max(f[i - 1], g[i - 1])
3.初始化
由于填表时总是需要前一个位置的值,所以需要初始化 f[0] 和 g[0] 的值:
- f[0] = num_countadd_set[0];
- g[0] = 0;
4.填表顺序
从左往右,两表同填
5.返回值
dp表最后一个位置对应的值就是整个数组满足题意的最大点数,所以返回值为:
max(f[N - 1], g[N - 1]);
示例代码
class Solution {
public:int deleteAndEarn(vector<int>& nums) {int n = nums.size();const int N = 10001;int num_countadd_set[N] = {0};for(auto e: nums){num_countadd_set[e] += e;}vector<int> f(N); // 选nums[i], 最大分钟数auto g = f; // 不选nums[i], 最大分钟数f[0] = num_countadd_set[0];g[0] = 0;for(int i = 1; i < N; i++){f[i] = g[i - 1] + num_countadd_set[i];g[i] = max(f[i - 1], g[i - 1]);}return max(f[N - 1], g[N - 1]);}
};
提交结果: