class Solution:
def deleteAndEarn(self, nums):
from collections import Counter
if not nums:
return 0
count = Counter(nums)
max_num = max(nums)
earn = [0] * (max_num + 1)
for num in range(len(earn)):
earn[num] = count.get(num, 0) * num
take, skip = 0, 0
for i in range(len(earn)):
take_i = skip + earn[i]
skip_i = max(skip, take)
take, skip = take_i, skip_i
return max(take, skip)
class Solution {
public:
int deleteAndEarn(vector<int>& nums) {
if (nums.empty()) return 0;
int maxNum = *max_element(nums.begin(), nums.end());
vector<int> sum(maxNum + 1, 0);
for (int n : nums) {
sum[n] += n;
}
int take = 0, skip = 0;
for (int i = 0; i <= maxNum; ++i) {
int take_i = skip + sum[i];
int skip_i = max(skip, take);
take = take_i;
skip = skip_i;
}
return max(take, skip);
}
};
class Solution {
public int deleteAndEarn(int[] nums) {
if (nums.length == 0) return 0;
int maxNum = 0;
for (int n : nums) maxNum = Math.max(maxNum, n);
int[] sum = new int[maxNum + 1];
for (int n : nums) sum[n] += n;
int take = 0, skip = 0;
for (int i = 0; i <= maxNum; i++) {
int take_i = skip + sum[i];
int skip_i = Math.max(skip, take);
take = take_i;
skip = skip_i;
}
return Math.max(take, skip);
}
}
var deleteAndEarn = function(nums) {
if (nums.length === 0) return 0;
let maxNum = Math.max(...nums);
let sum = new Array(maxNum + 1).fill(0);
for (let n of nums) sum[n] += n;
let take = 0, skip = 0;
for (let i = 0; i <= maxNum; ++i) {
let take_i = skip + sum[i];
let skip_i = Math.max(skip, take);
take = take_i;
skip = skip_i;
}
return Math.max(take, skip);
};
Given an array of integers called nums
, you can perform the following operation any number of times:
pick any number x
from nums
and earn x
points. However, after picking x
,
all instances of x-1
and x+1
are also deleted from the array and cannot be used again.
Your task is to maximize the total points you can earn by repeating this operation optimally until the array is empty.
nums
can be used at most once (when picked).nums
.
At first glance, this problem looks like a greedy selection problem: always pick the largest number to maximize points. However, because picking a number removes its neighbors (x-1
and x+1
), a greedy approach can fail. For example, picking a large number might prevent you from picking several smaller numbers whose total is greater.
This constraint is similar to the classic "House Robber" problem: you cannot pick adjacent houses. Here, the "houses" are numbers, and adjacency is defined by the number's value, not their position in the array.
The brute-force approach (trying all combinations) is too slow. We need a way to efficiently decide, for each possible number, whether to "take" it (and lose its neighbors) or "skip" it (and keep the option to take its neighbors).
Let's break down the optimized solution step by step:
nums
, calculate its total contribution (number of times it appears multiplied by its value).earn[i]
be the total points for number i
.take
be the max points if you take the current number, and skip
be the max if you skip it.nums
:
i
, you must have skipped i-1
: take_i = skip + earn[i]
.i
, take the max of previous take
and skip
: skip_i = max(skip, take)
.max(take, skip)
.
Why use an array? By using an array indexed by number value (from 0 to max in nums
), we make lookups and updates O(1) for each possible number.
Let's use the input nums = [3, 4, 2]
.
earn = [0, 0, 2, 3, 4]
nums
.nums
(since we process each possible number once).
This is efficient because even if n
is large, M
(the max number) is often manageable.
The "Delete and Earn" problem is solved efficiently by transforming it into a dynamic programming problem similar to "House Robber." By aggregating the points for each number and using a simple DP recurrence (take or skip), we avoid brute-force enumeration and achieve an optimal solution. The key insight is to realize that picking a number removes its neighbors, just like robbing a house prevents robbing adjacent houses. This approach is not only efficient but also elegantly leverages classic DP patterns.