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

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()

猜你喜欢

转载自blog.csdn.net/weixin_37780776/article/details/120806107