740. 删除并获得点数
问题描述
给你一个整数数组 nums,你可以执行以下操作任意次:
- 选择数组中的任何一个元素,删除它并获得
nums[i]的点数 - 删除
nums[i]后,你必须同时删除所有等于nums[i] - 1和nums[i] + 1的元素
你的目标是最大化获得的点数。
示例:
输入: nums = [3,4,2]
输出: 6
解释:
删除 4 获得 4 分,同时删除 3
删除 2 获得 2 分
总共获得 6 分
输入: nums = [2,2,3,3,3,4]
输出: 9
解释:
删除 3 获得 3×3 = 9 分,同时删除所有 2 和 4
算法思路
动态规划 + 预处理:
- 将问题转化为"打家劫舍"问题
- 统计每个数字的总点数(数字×频次)
- 由于删除一个数字会同时删除其相邻数字,这类似于不能选择相邻元素
- 使用动态规划求解最大点数
核心思想:将原问题转换为在连续整数序列上选择不相邻元素的最大和问题。
代码实现
方法一:动态规划(推荐解法)
import java.util.*;
class Solution {
/**
* 计算删除操作能获得的最大点数
*
* @param nums 整数数组
* @return 最大点数
*/
public int deleteAndEarn(int[] nums) {
// 输入校验
if (nums == null || nums.length == 0) {
return 0;
}
if (nums.length == 1) {
return nums[0];
}
// 步骤1:统计每个数字的总点数
// key: 数字, value: 该数字的总点数(数字×出现次数)
Map<Integer, Integer> points = new HashMap<>();
int maxNum = 0; // 记录最大数字
for (int num : nums) {
points.put(num, points.getOrDefault(num, 0) + num);
maxNum = Math.max(maxNum, num);
}
// 步骤2:使用动态规划求解
// dp[i] 表示考虑数字1到i时能获得的最大点数
// 由于数字可能不连续,按顺序处理到maxNum
int prev = 0; // dp[i-2]
int curr = 0; // dp[i-1]
// 从1遍历到maxNum
for (int i = 1; i <= maxNum; i++) {
int take = points.getOrDefault(i, 0) + prev; // 选择数字i
int skip = curr; // 不选择数字i
int next = Math.max(take, skip);
prev = curr;
curr = next;
}
return curr;
}
}
方法二:优化(数组代替哈希表)
class Solution {
/**
* 优化:使用数组代替哈希表
* 当数字范围较小时效率更高
*
* @param nums 输入数组
* @return 最大点数
*/
public int deleteAndEarn(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
// 找到最大值以确定数组大小
int maxNum = 0;
for (int num : nums) {
maxNum = Math.max(maxNum, num);
}
// 创建数组存储每个数字的总点数
int[] points = new int[maxNum + 1];
for (int num : nums) {
points[num] += num;
}
// 动态规划
if (maxNum == 1) {
return points[1];
}
// dp[i] = max(dp[i-1], dp[i-2] + points[i])
int prev = 0; // dp[i-2]
int curr = points[1]; // dp[i-1]
for (int i = 2; i <= maxNum; i++) {
int next = Math.max(curr, prev + points[i]);
prev = curr;
curr = next;
}
return curr;
}
}
方法三:空间优化
class Solution {
/**
* 空间优化:只用两个变量
*
* @param nums 输入数组
* @return 最大点数
*/
public int deleteAndEarn(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
// 统计频次
Map<Integer, Integer> count = new HashMap<>();
int maxNum = 0;
for (int num : nums) {
count.put(num, count.getOrDefault(num, 0) + 1);
maxNum = Math.max(maxNum, num);
}
// 只考虑存在的数字,按顺序处理
List<Integer> sortedNums = new ArrayList<>(count.keySet());
Collections.sort(sortedNums);
// 动态规划,只用两个变量
int prevMax = 0; // 前一个数字的最大点数
int currMax = 0; // 当前数字的最大点数
int prevNum = -2; // 上一个处理的数字
for (int num : sortedNums) {
int currentPoints = num * count.get(num);
int temp = currMax;
if (num - prevNum == 1) {
// 相邻数字,不能同时选择
currMax = Math.max(currMax, prevMax + currentPoints);
} else {
// 不相邻,可以都选择
currMax = currMax + currentPoints;
}
prevMax = temp;
prevNum = num;
}
return currMax;
}
}
算法分析
-
时间复杂度:O(n + m)
- n 是数组长度,m 是最大数字
- 需要遍历数组统计,然后遍历到最大数字
-
空间复杂度:
- 方法一:O(m),哈希表和DP空间
- 方法二:O(m),数组空间
- 方法三:O(n),只存储存在的数字
-
算法特点:
- 转换为经典DP问题
- 状态压缩优化空间
- 贪心选择策略
算法过程
nums = [2,2,3,3,3,4] :
-
统计点数:
2: 2×2 = 43: 3×3 = 94: 4×1 = 4
-
动态规划:
i=1:dp[1]=0(无1)i=2:dp[2]=max(dp[1], dp[0]+4)=max(0,0+4)=4i=3:dp[3]=max(dp[2], dp[1]+9)=max(4,0+9)=9i=4:dp[4]=max(dp[3], dp[2]+4)=max(9,4+4)=9
-
返回:9
测试用例
public static void main(String[] args) {
Solution solution = new Solution();
// 测试用例1:标准情况
int[] nums1 = {3,4,2};
System.out.println("Test 1: " + solution.deleteAndEarn(nums1)); // 6
// 测试用例2:重复元素
int[] nums2 = {2,2,3,3,3,4};
System.out.println("Test 2: " + solution.deleteAndEarn(nums2)); // 9
// 测试用例3:单元素
int[] nums3 = {1};
System.out.println("Test 3: " + solution.deleteAndEarn(nums3)); // 1
// 测试用例4:空数组
int[] nums4 = {};
System.out.println("Test 4: " + solution.deleteAndEarn(nums4)); // 0
// 测试用例5:递增序列
int[] nums5 = {1,2,3};
System.out.println("Test 5: " + solution.deleteAndEarn(nums5)); // 4 (选1和3)
// 测试用例6:大数字
int[] nums6 = {10,10,10};
System.out.println("Test 6: " + solution.deleteAndEarn(nums6)); // 30
// 测试用例7:负数
int[] nums7 = {-1,0,1};
System.out.println("Test 7: " + solution.deleteAndEarn(nums7)); // 0 (选0)
}
关键点
-
问题转换:
- 将删除操作转换为选择问题
- 相邻数字不能同时选择
-
点数计算:
- 同一数字的点数可以累加
- 删除一个数字获得其所有出现的点数
-
动态规划状态:
dp[i]表示考虑1到i的最大点数- 转移方程:
dp[i] = max(dp[i-1], dp[i-2] + points[i])
-
边界处理:
- 空数组
- 单元素
- 负数
常见问题
-
为什么能转换为打家劫舍问题?
- 因为选择一个数字就不能选择其相邻数字
- 结构完全相同
-
如何处理负数?
- 算法同样适用
- 负数的点数为负,通常不会选择
-
能否用贪心?
- 不能,
局部最优不等于全局最优
- 不能,
-
最大可能的点数?
- 所有正数的点数和
-
如何返回选择方案?**
- 需要记录选择路径
动态规划解决删除并获得点数问题
4万+

被折叠的 条评论
为什么被折叠?



