740. 删除并获得点数 - medium
给你一个整数数组 nums
,你可以对它进行一些操作。
每次操作中,选择任意一个 nums[i]
,删除它并获得 nums[i]
的点数。之后,你必须删除每个等于 nums[i] - 1
或 nums[i] + 1
的元素。
开始你拥有 0
个点数。返回你能通过这些操作获得的最大点数。
示例 1:
输入:nums = [3,4,2] 输出:6 解释: 删除 4 获得 4 个点数,因此 3 也被删除。 之后,删除 2 获得 2 个点数。总共获得 6 个点数。
示例 2:
输入:nums = [2,2,3,3,3,4] 输出:9 解释: 删除 3 获得 3 个点数,接着要删除两个 2 和 4 。 之后,再次删除 3 获得 3 个点数,再次删除 3 获得 3 个点数。 总共获得 9 个点数。
提示:
1 <= nums.length <= 2 * 104
1 <= nums[i] <= 104
先将 nums 排序。接收两个参数:当前数下标 i 和上一次选择的数字 v。如果当前数等于 v + 1,当前数不可选。否则返回选择和不选择当前数和较大的情况。
class Solution:
def deleteAndEarn(self, nums: List[int]) -> int:
nums.sort()
n = len(nums)
@functools.cache
def dfs(i: int, v: int) -> int:
if i == n:
return 0
j = i
while j < n and nums[i] == nums[j]:
j += 1
s1 = dfs(j, -1)
if nums[i] == v + 1:
return s1
s2 = dfs(j, nums[i]) + nums[i] * (j - i)
return max(s1, s2)
return dfs(0, -1)
类似于 198. 打家劫舍
每个数有两种状态,选择 & 不选择。
- 选择 s1 分为两种情况,
nums[i] == nums[i - 1] + 1
,如果 true,则只能从前一天不选择状态转移。否则从 max(s1, s2) 状态转移。 - 不选择 s2 直接取 max(s1, s2)
时间复杂度 O(n logn), 空间复杂度 O(1)
class Solution:
def deleteAndEarn(self, nums: List[int]) -> int:
n = len(nums)
if n == 0:
return 0
nums.sort()
s1, s2 = 0, 0
i = 0
while i < n:
v = nums[i]
j = i
while j < n and nums[j] == v:
j += 1
bs = s2
s2 = max(s1, s2)
if nums[i - 1] + 1 != v:
bs = s2
s1 = bs + v * (j - i)
i = j
return max(s1, s2)
参考题解。使用 total[v] 表示选择数字 v 增加总点数。将问题转变为一个 [0, max(nums)] 之间不可以连续选择的问题(打家劫舍问题)。
使用 llv
表示上上数总的收益,lv
表示上一个数总收益。则有:
- 当前数字选择,只能从上上数选。即
llv + ns[i]
。当前数不选,收益为lv
- 当前数最大收益为 max(llv + ns[i], lv)
时间复杂度为 O(n+m), 空间复杂度为 O(m)
class Solution:
def deleteAndEarn(self, nums: List[int]) -> int:
maxv = max(nums)
total = [0] * (maxv + 1)
for v in nums:
total[v] += v
def rob(ns: list) -> int:
n = len(ns)
llv, lv = ns[0], max(ns[0], ns[1])
for i in range(2, n):
llv, lv = lv, max(llv + ns[i], lv)
return lv
return rob(total)