你写的回溯算法是无法通过添加 memo 改成动态规划的。因为你的回溯算法的状态是有后效性的。即后续的状态和之前的状态是相关的。因为在每次操作 delete(nums, index) 的时候,下一次能删除哪个元素,和之前删除过哪个元素息息相关。
请仔细体会一下,你的这个问题确实是动态规划的难点,也是重点。对于动态规划来说,怎么定义状态至关重要。不是能写出回溯算法就能改成动态规划的。
这个问题可以怎么定义状态,请理解一下我的参考代码和对应的状态定义方式(由于我手头的系统写 C++ 方便,所以以下是 C++ 代码,你可以在理解的基础上,改成 Java 代码):
class Solution {
public:
int deleteAndEarn(vector<int>& nums) {
// 对 nums 的排序不影响最终结果。
// 这里为什么要对 nums 排序?请将其作为一个思考题,在理解了下面的算法后,回过头回答这个问题。
sort(nums.begin(), nums.end());
vector<int> memo(nums.size(), -1);
return dfs(nums, 0, memo);
}
private:
// dfs(nums, index) 的定义是,下面在 nums[index...nums.size()) 的范围里删除元素,
// 可以得到的最大分值是多少
int dfs(const vector<int>& nums, int index, vector<int>& memo){
// 如果 index >= nums.size(),一个元素都删不了,分值为 0
if(index >= nums.size())
return 0;
// 记忆化搜索。如果 memo[index] 不为 -1,
// 则之前计算过 nums[index...nums.size()) 的范围里删除元素可以得到的最大分值
// 直接返回
if(memo[index] != -1)
return memo[index];
// 索引 i1 指向比 nums[index] 大的下一个元素的索引
// 如果 nums[index...nums.size()) 为 2, 2, 2, 3, 3, 5, 6
// 则 i1 指向第一个 3 的索引
int i1;
for(i1 = index; i1 < nums.size() && nums[i1] == nums[index]; i1 ++);
// 索引 i2 指向比 nums[index] + 1 大的下一个元素的索引
// 如果 nums[index...nums.size()) 为 2, 2, 2, 3, 3, 5, 6
// 则 i2 指向第一个 5 的索引
// 如果 nums[index...nums.size()) 为 2, 2, 2, 4, 4, 5, 6
// 则 i2 指向第一个 4 的索引,此时 i2 和 i1 一致
int i2;
for(i2 = i1; i2 < nums.size() && nums[i2] == nums[index] + 1; i2 ++);
// 面对当前 在 nums[index...nums.size()) 的范围里删除元素,有两个选择
// 选择 1,选择删除 nums[index] 以及所有和 nums[index] 相同的元素。
// 整个数组中一共有 (i1 - index) 个 nums[index] 这么大的元素,分值加上 nums[index] * (i1 - index)
// 之后,数组中 nums[i1...i2) 这个范围里的元素不能选了,因为他们比 nums[index] 大一
// 之后,只要计算在 nums[i2...nums.size()) 的范围里删除元素可以得到的最大分值即可
int res1 = nums[index] * (i1 - index) + dfs(nums, i2, memo);
// 选择 2,不选择删除 nums[index]
// 之后,只要计算在 nums[i1...nums.size()) 的范围里删除元素可以得到的最大分值即可
int res2 = dfs(nums, i1, memo);
return memo[index] = max(res1, res2);
}
};
请仔细理解在上述逻辑的状态定义中,这个状态是无后效性的。也就是当我面对在 nums[index....nums.size()) 这个子数组中删除元素,找最大分值的时候,是和在 nums[0...index) 这个区间里怎么删除元素,完全无关的。
继续加油!:)