算法题 删除并获得点数

动态规划解决删除并获得点数问题

740. 删除并获得点数

问题描述

给你一个整数数组 nums,你可以执行以下操作任意次:

  • 选择数组中的任何一个元素,删除它并获得 nums[i] 的点数
  • 删除 nums[i] 后,你必须同时删除所有等于 nums[i] - 1nums[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

算法思路

动态规划 + 预处理

  1. 将问题转化为"打家劫舍"问题
  2. 统计每个数字的总点数(数字×频次)
  3. 由于删除一个数字会同时删除其相邻数字,这类似于不能选择相邻元素
  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]

  1. 统计点数

    • 2: 2×2 = 4
    • 3: 3×3 = 9
    • 4: 4×1 = 4
  2. 动态规划

    • i=1: dp[1]=0(无1)
    • i=2: dp[2]=max(dp[1], dp[0]+4)=max(0,0+4)=4
    • i=3: dp[3]=max(dp[2], dp[1]+9)=max(4,0+9)=9
    • i=4: dp[4]=max(dp[3], dp[2]+4)=max(9,4+4)=9
  3. 返回: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)
}

关键点

  1. 问题转换

    • 将删除操作转换为选择问题
    • 相邻数字不能同时选择
  2. 点数计算

    • 同一数字的点数可以累加
    • 删除一个数字获得其所有出现的点数
  3. 动态规划状态

    • dp[i] 表示考虑1到i的最大点数
    • 转移方程:dp[i] = max(dp[i-1], dp[i-2] + points[i])
  4. 边界处理

    • 空数组
    • 单元素
    • 负数

常见问题

  1. 为什么能转换为打家劫舍问题?

    • 因为选择一个数字就不能选择其相邻数字
    • 结构完全相同
  2. 如何处理负数?

    • 算法同样适用
    • 负数的点数为负,通常不会选择
  3. 能否用贪心?

    • 不能,局部最优不等于全局最优
  4. 最大可能的点数?

    • 所有正数的点数和
  5. 如何返回选择方案?**

    • 需要记录选择路径
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值