【LeetCode 动态规划专项】删除并获得点数(740)

本文解析了如何利用动态规划解决LeetCode题目,通过删除数组中的元素并遵循特定规则获取最大点数。关键步骤包括构建sums数组,将其转化为子序列和问题,并与打家劫舍问题相联系。展示了两种解法及其复杂度分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 题目

给你一个整数数组 nums ,你可以对它进行一些操作。每次操作中,选择任意一个 nums[i] ,删除它并获得 nums[i] 的点数。之后,你必须删除所有等于 nums[i] - 1nums[i] + 1 的元素。开始你拥有 0 0 0 个点数。返回你能通过这些操作获得的最大点数

1.1 示例

  • 示例 1 1 1

    • 输入: nums = [3, 4, 2]
    • 输出: 6 6 6
    • 解释: 删除 4 4 4 获得 4 4 4 个点数,按规则 3 3 3 也被删除;之后,删除 2 2 2 获得 2 2 2 个点数。总共获得 6 6 6 个点数。
  • 示例 2 2 2

    • 输入: nums = [2, 2, 3, 3, 3, 4]
    • 输出: 9 9 9
    • 解释: 删除 3 3 3 获得 3 3 3 个点数,按规则接着要删除两个 2 2 2 4 4 4 ;之后,再次删除 3 3 3 获得 3 3 3 个点数,再次删除 3 3 3 获得 3 3 3 个点数。总共获得 9 9 9 个点数。

1.2 说明

1.3 提示

  • 1 ≤ nums.length ≤ 2 ∗ 1 0 4 1 \le \text{nums.length} \le 2 * 10^4 1nums.length2104
  • 1 ≤ nums[i] ≤ 1 0 4 1 \le \text{nums[i]} \le 10^4 1nums[i]104

1.4 进阶

你可以进一步给出删除元素的顺序么?

2. 解法一(动态规划)

2.1 分析

根据题意,在选择了元素 x 后,该元素以及所有等于 x − 1x + 1 的元素会从数组中被删去(即无法再被选择)。若还有多个值为 x 的元素,还可以可以直接删除 x 并获得其点数。因此若选择了 x,所有等于 x 的元素也应一同被选择,以尽可能多地获得点数。

如果记元素 x 在数组中出现的次数为 count_x ,我们可以用一个数组 sums 记录数组 nums 中所有相同元素之和,即 sums[x] = x * count_x 。若选择了 x,则可以获取 sums[count_x] 的点数,这也意味着无法再选择 x − 1x + 1

在统计出 sums 数组后,实际上问题就转化成了:给定一个数组 sums ,要求获取该数组的最大子序列和,对于这样的子序列还要求任意两个元素在 sums 中的下标不能相邻。

实际上,通过上述分析,问题就转换成了和「【LeetCode 动态规划专项】打家劫舍(198)」相同的问题,即所谓子序列的任意两个元素在数组 sums 中不相邻等价于不同行窃两个相邻的房子。

2.2 解答

根据上述分析,再复用「【LeetCode 动态规划专项】打家劫舍(198)」的解答,可以给出下列求解:

from typing import List


class Solution:
    def _iterative_rob(self, nums: List[int]) -> int:
        if len(nums) == 1:
            return nums[0]
        if len(nums) == 2:
            return max(nums[0], nums[1])
        dp = [0 for _ in range(len(nums))]
        dp[0] = nums[0]
        dp[1] = max(nums[0], nums[1])
        for i in range(2, len(nums)):
            dp[i] = max(dp[i - 2] + nums[i], dp[i - 1])
        return dp[-1]

    def delete_and_earn(self, nums: List[int]) -> int:
        sums = [0 for _ in range(max(nums) + 1)]
        for num in nums:
            sums[num] += num
        return self._iterative_rob(sums)


def main():
    nums = [2, 2, 3, 3, 3, 4]
    sln = Solution()
    print(sln.delete_and_earn(nums))  # 9


if __name__ == '__main__':
    main()

2.3 复杂度

  • 时间复杂度: O ( n + m ) O(n+m) O(n+m),其中 n n n 是数组 nums 的长度, m m mnums 中元素的最大值;
  • 空间复杂度: O ( n + m ) O(n+m) O(n+m)

实际上,由于「【LeetCode 动态规划专项】打家劫舍(198)」中可以将空间复杂度降低至 O ( 1 ) O(1) O(1) ,所以复用空间复杂度更低的 _efficient_iterative_rob 方法可以进一步将空间复杂度降低至 O ( m ) O(m) O(m)

from typing import List


class Solution:
    def _efficient_iterative_rob(self, nums: List[int]) -> int:
        if len(nums) == 1:
            return nums[0]
        if len(nums) == 2:
            return max(nums[0], nums[1])
        prev, cur, nxt = nums[0], max(nums[0], nums[1]), 0
        for i in range(2, len(nums)):
            nxt = max(prev + nums[i], cur)
            prev, cur = cur, nxt
        return cur

    def delete_and_earn(self, nums: List[int]) -> int:
        sums = [0 for _ in range(max(nums) + 1)]
        for num in nums:
            sums[num] += num
        return self._efficient_iterative_rob(sums)


def main():
    nums = [2, 2, 3, 3, 3, 4]
    sln = Solution()
    print(sln.delete_and_earn(nums))


if __name__ == '__main__':
    main()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值