LeetCode 740 - Delete and Earn
The problem gives us an integer array nums, and we want to maximize the total number of points earned by repeatedly deleting elements. When we delete a number x, we gain x points. However, deleting x also forces the removal of every occurrence of x - 1 and x + 1.
Difficulty: 🟡 Medium
Topics: Array, Hash Table, Dynamic Programming
Solution
Problem Understanding
The problem gives us an integer array nums, and we want to maximize the total number of points earned by repeatedly deleting elements.
When we delete a number x, we gain x points. However, deleting x also forces the removal of every occurrence of x - 1 and x + 1. This restriction is the core challenge of the problem because choosing one value prevents us from choosing its neighboring values.
The important detail is that deleting a single occurrence of a number does not just affect one neighboring element. It removes all instances of adjacent values globally. Because of this, the order of deletions is less important than deciding which values we ultimately keep.
For example, if the array contains several 3s, then taking 3 multiple times may be better than taking 2s and 4s combined. The problem becomes a tradeoff between neighboring values.
The input consists of:
- An integer array
nums - Each value is between
1and10^4 - The array length can be as large as
2 * 10^4
The output is a single integer representing the maximum number of points obtainable.
The constraints are important because they rule out exponential solutions. A brute-force search over all deletion combinations would become infeasible very quickly. Since the value range is limited to 10^4, this suggests that grouping numbers by value may be useful.
Several edge cases are important:
- Arrays containing only one distinct value
- Arrays where all values are consecutive
- Arrays with large gaps between values
- Arrays with many duplicates
- Very small arrays, such as length
1
A naive implementation might repeatedly modify arrays or recursively try every possible deletion sequence, which would be extremely slow.
Approaches
Brute Force Approach
A brute-force solution would try every possible deletion order.
At each step, we could pick any remaining number, gain its value, remove all instances of neighboring values, and recursively compute the best possible future score. We would then take the maximum across all choices.
This approach is correct because it explores every valid sequence of operations. Eventually, it finds the optimal one.
However, it is far too slow. The number of possible states grows exponentially because each distinct value creates branching choices. Even with memoization, tracking all possible remaining sets becomes expensive.
The constraints make this approach impractical.
Key Insight
The key observation is that the exact positions of numbers do not matter. Only the total contribution of each value matters.
Suppose the array contains:
- three
2s - four
3s - two
4s
Then:
- taking all
2s earns6 - taking all
3s earns12 - taking all
4s earns8
If we choose value 3, we cannot choose 2 or 4.
This transforms the problem into a classic dynamic programming pattern identical to the House Robber problem.
We create an array where:
points[x] = x * frequency(x)
Now the decision becomes:
- take value
x, then skipx - 1 - skip value
x
This is exactly the same as deciding whether to rob adjacent houses.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(2^n) | O(n) | Tries every deletion combination recursively |
| Optimal | O(n + k) | O(k) | Dynamic programming over value frequencies |
Here:
nis the length ofnumskis the maximum value innums
Algorithm Walkthrough
Optimal Dynamic Programming Algorithm
- Count how many times each number appears.
We first compute the frequency of every value in nums. This allows us to determine the total points obtainable from each distinct number.
For example:
nums = [2,2,3,3,3,4]
frequency:
2 -> 2
3 -> 3
4 -> 1
- Build a points array.
For every value x, compute:
points[x] = x * frequency[x]
In the example above:
points[2] = 4
points[3] = 9
points[4] = 4
This converts the problem from individual elements into grouped values. 3. Apply dynamic programming.
Define:
dp[i] = maximum points obtainable using values from 0 to i
At each value i, we have two choices:
- Skip
i - Take
i, which means we cannot takei - 1
Therefore:
$dp[i]=\max(dp[i-1],\ dp[i-2]+points[i])$ 4. Initialize base cases.
We need starting values for the recurrence.
dp[0] = 0
dp[1] = points[1]
- Iterate through all possible values.
Since values are bounded by 10^4, we iterate from 2 up to the maximum number in nums.
For each value:
dp[i - 1]means we skipidp[i - 2] + points[i]means we takei
- Return the final answer.
The result is stored in:
dp[max_value]
Why it works
The algorithm works because every value only conflicts with its immediate neighbors.
Once the array is transformed into total points per value, the problem becomes equivalent to selecting non-adjacent numbers for maximum sum. The dynamic programming recurrence guarantees that at every step we consider both valid possibilities:
- excluding the current value
- including the current value while excluding its neighbor
Since the recurrence always keeps the best achievable score up to each value, the final answer is optimal.
Python Solution
from typing import List
class Solution:
def deleteAndEarn(self, nums: List[int]) -> int:
max_value = max(nums)
points = [0] * (max_value + 1)
for num in nums:
points[num] += num
if max_value == 1:
return points[1]
dp = [0] * (max_value + 1)
dp[0] = 0
dp[1] = points[1]
for value in range(2, max_value + 1):
dp[value] = max(
dp[value - 1],
dp[value - 2] + points[value]
)
return dp[max_value]
The implementation begins by finding the maximum value in the input array. This determines the size of the points and dp arrays.
The points array stores the total score obtainable from each number. Instead of tracking frequency separately, we directly accumulate the value itself:
points[num] += num
For example, if 3 appears three times, then:
points[3] = 9
This simplifies the later dynamic programming logic.
The special case for max_value == 1 prevents out-of-bounds issues and correctly handles arrays containing only 1s.
The dp array stores the best possible score up to each value. The recurrence compares:
- skipping the current value
- taking the current value plus the best score from two positions earlier
Finally, the algorithm returns the best score for the largest value.
Go Solution
func deleteAndEarn(nums []int) int {
maxValue := 0
for _, num := range nums {
if num > maxValue {
maxValue = num
}
}
points := make([]int, maxValue+1)
for _, num := range nums {
points[num] += num
}
if maxValue == 1 {
return points[1]
}
dp := make([]int, maxValue+1)
dp[0] = 0
dp[1] = points[1]
for value := 2; value <= maxValue; value++ {
take := dp[value-2] + points[value]
skip := dp[value-1]
if take > skip {
dp[value] = take
} else {
dp[value] = skip
}
}
return dp[maxValue]
}
The Go implementation follows the same logic as the Python version.
Since Go does not provide a built-in max function for integers, we manually compare values using an if statement.
Slices are used for both the points and dp arrays. Go initializes slices with zero values automatically, which matches the desired behavior.
Integer overflow is not an issue because the maximum possible score is well within Go's integer range.
Worked Examples
Example 1
nums = [3,4,2]
Step 1: Build points array
| Value | Total Points |
|---|---|
| 2 | 2 |
| 3 | 3 |
| 4 | 4 |
Step 2: Initialize DP
| i | dp[i] |
|---|---|
| 0 | 0 |
| 1 | 0 |
Step 3: Fill DP
| Value | Take | Skip | dp[value] |
|---|---|---|---|
| 2 | 0 + 2 = 2 | 0 | 2 |
| 3 | 0 + 3 = 3 | 2 | 3 |
| 4 | 2 + 4 = 6 | 3 | 6 |
Final answer:
6
Example 2
nums = [2,2,3,3,3,4]
Step 1: Build points array
| Value | Total Points |
|---|---|
| 2 | 4 |
| 3 | 9 |
| 4 | 4 |
Step 2: Initialize DP
| i | dp[i] |
|---|---|
| 0 | 0 |
| 1 | 0 |
Step 3: Fill DP
| Value | Take | Skip | dp[value] |
|---|---|---|---|
| 2 | 0 + 4 = 4 | 0 | 4 |
| 3 | 0 + 9 = 9 | 4 | 9 |
| 4 | 4 + 4 = 8 | 9 | 9 |
Final answer:
9
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n + k) | Build frequency totals in O(n), then iterate through values up to max number |
| Space | O(k) | Arrays for points and DP depend on maximum value |
Here:
nis the number of elements innumskis the maximum value innums
The algorithm is efficient because the value range is bounded. Instead of exploring deletion sequences directly, we aggregate equal values and solve a one-dimensional dynamic programming problem.
Test Cases
solution = Solution()
assert solution.deleteAndEarn([3, 4, 2]) == 6
# Basic example from the problem statement
assert solution.deleteAndEarn([2, 2, 3, 3, 3, 4]) == 9
# Multiple duplicates with adjacent conflicts
assert solution.deleteAndEarn([1]) == 1
# Single element array
assert solution.deleteAndEarn([1, 1, 1, 1]) == 4
# All identical values
assert solution.deleteAndEarn([1, 2, 3, 4, 5]) == 9
# Consecutive values
assert solution.deleteAndEarn([8, 10, 4, 9, 1, 3, 5, 9, 4, 10]) == 37
# Mixed values with multiple optimal choices
assert solution.deleteAndEarn([2, 2, 2, 2]) == 8
# Only one distinct number
assert solution.deleteAndEarn([1, 3, 5, 7]) == 16
# No adjacent conflicts at all
assert solution.deleteAndEarn([10000]) == 10000
# Maximum value boundary
assert solution.deleteAndEarn([9999, 10000]) == 10000
# Adjacent high values
| Test | Why |
|---|---|
[3,4,2] |
Validates the basic example |
[2,2,3,3,3,4] |
Tests duplicate aggregation |
[1] |
Smallest possible input |
[1,1,1,1] |
All identical values |
[1,2,3,4,5] |
Fully consecutive values |
[8,10,4,9,1,3,5,9,4,10] |
Complex decision structure |
[2,2,2,2] |
Single distinct value |
[1,3,5,7] |
No adjacency conflicts |
[10000] |
Maximum value constraint |
[9999,10000] |
Adjacent large numbers |
Edge Cases
One important edge case is when the array contains only a single distinct value, such as [2,2,2,2]. A buggy implementation might still attempt to apply adjacency logic unnecessarily. In this case, the correct strategy is simply to take every occurrence. The implementation handles this naturally because the dynamic programming recurrence treats missing neighboring values as zero.
Another important edge case is when every number is consecutive, such as [1,2,3,4,5]. This creates the maximum number of conflicts because choosing any value blocks its neighbors. A greedy strategy could fail here by choosing locally large values instead of globally optimal combinations. The dynamic programming solution correctly evaluates both taking and skipping each value.
A third edge case occurs when there are large gaps between values, such as [1,3,5,7]. Since none of these values are adjacent, all of them can be taken together. Some implementations incorrectly assume every step involves a conflict. The DP formulation handles this correctly because values with zero points simply carry forward previous results.
Another subtle edge case is arrays containing only 1s. Since the recurrence references dp[i - 2], implementations without careful initialization may encounter index errors. The explicit handling of max_value == 1 ensures correctness for this scenario.