740. Delete and Earn
Problem Description
You have an integer array nums
and want to maximize your points through a strategic deletion process.
The rules are:
- You can pick any element
nums[i]
from the array and delete it, earningnums[i]
points - When you delete
nums[i]
, you must also delete ALL elements with valuenums[i] - 1
and ALL elements with valuenums[i] + 1
from the array (without earning points for these forced deletions) - You can repeat this operation as many times as you want with the remaining elements
Your goal is to find the maximum total points you can earn.
For example, if nums = [3, 4, 2]
:
- If you delete
4
, you earn 4 points but must also remove all3
s and5
s (the 3 gets removed without earning points) - You're left with
[2]
, which you can delete for 2 more points - Total: 4 + 2 = 6 points
The challenge is determining which elements to delete and in what order to maximize your total score, considering that choosing certain values forces you to sacrifice adjacent values.
Intuition
The key insight is that when we choose to take a number x
, we automatically forfeit all instances of x-1
and x+1
. This means we face a choice: either take all occurrences of x
(earning x * count(x)
points) or skip x
entirely.
Since we can't take consecutive numbers, this problem transforms into something familiar. If we group all identical numbers together and calculate their total value, we get an array where total[i]
represents the sum of all occurrences of number i
.
For example, if nums = [2, 2, 3, 3, 3, 4]
:
total[2] = 4
(two 2's)total[3] = 9
(three 3's)total[4] = 4
(one 4)
Now the problem becomes: given an array where we can't pick adjacent elements, what's the maximum sum we can achieve? This is the classic "House Robber" problem!
At each number i
, we have two choices:
- Take number
i
: We gettotal[i]
points plus the best we could do up toi-2
(since we can't takei-1
) - Skip number
i
: We keep the best result up toi-1
We choose whichever gives more points: max(total[i] + dp[i-2], dp[i-1])
This dynamic programming approach naturally handles the constraint that picking a number eliminates its neighbors, while ensuring we maximize our total score.
Learn more about Dynamic Programming patterns.
Solution Approach
The implementation follows our intuition of transforming this into a House Robber problem:
Step 1: Find the maximum value and create frequency sum array
mx = -inf
for num in nums:
mx = max(mx, num)
total = [0] * (mx + 1)
We first find the maximum value in nums
to determine the size of our total
array. The total
array will store the sum of all occurrences for each number from 0 to mx
.
Step 2: Calculate total points for each number
for num in nums: total[num] += num
For each number in the original array, we add its value to total[num]
. If number 3 appears four times, total[3]
will be 12.
Step 3: Apply House Robber dynamic programming
first = total[0]
second = max(total[0], total[1])
We initialize two variables:
first
: Maximum points we can get considering elements up to indexi-2
second
: Maximum points we can get considering elements up to indexi-1
Step 4: Iterate through remaining numbers
for i in range(2, mx + 1):
cur = max(first + total[i], second)
first = second
second = cur
For each position i
:
- Calculate
cur = max(first + total[i], second)
first + total[i]
: Take current number (add to best result fromi-2
)second
: Skip current number (keep best result fromi-1
)
- Update variables for next iteration:
first
becomes oldsecond
,second
becomescur
Step 5: Return the result
return second
The final second
contains the maximum points achievable.
This space-optimized approach uses O(max_value) space and O(n + max_value) time, where n is the length of the input array.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with nums = [2, 2, 3, 3, 3, 4]
:
Step 1: Find maximum value and create frequency sum array
- Maximum value in nums is 4
- Create
total
array of size 5:[0, 0, 0, 0, 0]
Step 2: Calculate total points for each number
- Process each number in nums:
- Two 2's:
total[2] = 2 + 2 = 4
- Three 3's:
total[3] = 3 + 3 + 3 = 9
- One 4:
total[4] = 4
- Two 2's:
- Result:
total = [0, 0, 4, 9, 4]
Step 3: Initialize House Robber variables
first = total[0] = 0
(best we can do up to index 0)second = max(total[0], total[1]) = max(0, 0) = 0
(best up to index 1)
Step 4: Dynamic programming iteration
i = 2:
cur = max(first + total[2], second) = max(0 + 4, 0) = 4
- We take all 2's (worth 4 points)
- Update:
first = 0
,second = 4
i = 3:
cur = max(first + total[3], second) = max(0 + 9, 4) = 9
- We take all 3's (worth 9 points), which means we can't take 2's or 4's
- Update:
first = 4
,second = 9
i = 4:
cur = max(first + total[4], second) = max(4 + 4, 9) = max(8, 9) = 9
- We skip 4's and keep our previous best of 9 (from taking all 3's)
- Update:
first = 9
,second = 9
Step 5: Return result
- Return
second = 9
The optimal strategy is to delete all instances of 3, earning 9 points total. We cannot take 2's or 4's because they are adjacent to 3.
Solution Implementation
1class Solution:
2 def deleteAndEarn(self, nums: List[int]) -> int:
3 # Find the maximum value in nums to determine array size
4 max_value = float('-inf')
5 for num in nums:
6 max_value = max(max_value, num)
7
8 # Create an array where index i stores the sum of all occurrences of value i
9 # This transforms the problem into a house robber-like problem
10 points = [0] * (max_value + 1)
11 for num in nums:
12 points[num] += num
13
14 # Dynamic programming approach similar to house robber problem
15 # prev_prev: max points we can earn up to index i-2
16 prev_prev = points[0]
17 # prev: max points we can earn up to index i-1
18 prev = max(points[0], points[1])
19
20 # For each value from 2 to max_value, decide whether to take it or skip it
21 for i in range(2, max_value + 1):
22 # Either take current value + max from i-2, or skip current and take max from i-1
23 current = max(prev_prev + points[i], prev)
24 # Update variables for next iteration
25 prev_prev = prev
26 prev = current
27
28 # Return the maximum points we can earn
29 return prev
30
1class Solution {
2 public int deleteAndEarn(int[] nums) {
3 // Handle edge case: empty array
4 if (nums.length == 0) {
5 return 0;
6 }
7
8 // Array to store the sum of all occurrences of each number
9 // Index represents the number, value represents total points for that number
10 int[] pointsByNumber = new int[10010];
11
12 // Dynamic programming arrays
13 // dpTake[i]: maximum points when we take number i
14 // dpSkip[i]: maximum points when we skip number i
15 int[] dpTake = new int[10010];
16 int[] dpSkip = new int[10010];
17
18 // Calculate total points for each number and find the maximum value
19 int maxNumber = 0;
20 for (int num : nums) {
21 pointsByNumber[num] += num;
22 maxNumber = Math.max(maxNumber, num);
23 }
24
25 // Dynamic programming approach
26 // For each number from 1 to maxNumber, decide whether to take it or skip it
27 for (int i = 1; i <= maxNumber; i++) {
28 // If we take number i, we cannot take i-1, so we use dpSkip[i-1]
29 // and add the points from taking all occurrences of i
30 dpTake[i] = dpSkip[i - 1] + pointsByNumber[i];
31
32 // If we skip number i, we can choose the maximum from the previous state
33 // (either we took i-1 or skipped i-1)
34 dpSkip[i] = Math.max(dpTake[i - 1], dpSkip[i - 1]);
35 }
36
37 // Return the maximum points achievable
38 return Math.max(dpTake[maxNumber], dpSkip[maxNumber]);
39 }
40}
41
1class Solution {
2public:
3 int deleteAndEarn(vector<int>& nums) {
4 // Create a frequency array where index represents the number
5 // and value represents the total points earned by taking all occurrences of that number
6 vector<int> pointsArray(10010);
7
8 // Calculate total points for each number
9 // If we take number 'num', we get num * frequency points
10 for (int& num : nums) {
11 pointsArray[num] += num;
12 }
13
14 // Transform problem into house robber problem
15 // Adjacent numbers cannot be taken (similar to adjacent houses)
16 return houseRobber(pointsArray);
17 }
18
19private:
20 int houseRobber(vector<int>& points) {
21 // Dynamic programming approach using two variables
22 // prevNotTaken: maximum points when previous element is not taken
23 // prevTaken: maximum points considering previous element (taken or not taken)
24 int prevNotTaken = 0;
25 int prevTaken = points[0];
26
27 // Iterate through all possible values
28 for (int i = 1; i < points.size(); ++i) {
29 // Current max is either:
30 // 1. Take current element + max points when previous wasn't taken
31 // 2. Don't take current element, keep previous max
32 int currentMax = max(points[i] + prevNotTaken, prevTaken);
33
34 // Update states for next iteration
35 prevNotTaken = prevTaken;
36 prevTaken = currentMax;
37 }
38
39 // Return the maximum points possible
40 return prevTaken;
41 }
42};
43
1function deleteAndEarn(nums: number[]): number {
2 // Create a frequency array where index represents the number
3 // and value represents the total points earned by taking all occurrences of that number
4 const pointsArray: number[] = new Array(10010).fill(0);
5
6 // Calculate total points for each number
7 // If we take number 'num', we get num * frequency points
8 for (const num of nums) {
9 pointsArray[num] += num;
10 }
11
12 // Transform problem into house robber problem
13 // Adjacent numbers cannot be taken (similar to adjacent houses)
14 return houseRobber(pointsArray);
15}
16
17function houseRobber(points: number[]): number {
18 // Dynamic programming approach using two variables
19 // prevNotTaken: maximum points when previous element is not taken
20 // prevTaken: maximum points considering previous element (taken or not taken)
21 let prevNotTaken: number = 0;
22 let prevTaken: number = points[0];
23
24 // Iterate through all possible values
25 for (let i = 1; i < points.length; i++) {
26 // Current max is either:
27 // 1. Take current element + max points when previous wasn't taken
28 // 2. Don't take current element, keep previous max
29 const currentMax: number = Math.max(points[i] + prevNotTaken, prevTaken);
30
31 // Update states for next iteration
32 prevNotTaken = prevTaken;
33 prevTaken = currentMax;
34 }
35
36 // Return the maximum points possible
37 return prevTaken;
38}
39
Time and Space Complexity
Time Complexity: O(n + m)
where n
is the length of the input array nums
and m
is the maximum value in nums
.
- Finding the maximum value:
O(n)
- iterates through all elements once - Building the
total
array:O(n)
- iterates through all elements to accumulate sums - Dynamic programming loop:
O(m)
- iterates from index 2 tomx
- Overall:
O(n) + O(n) + O(m) = O(n + m)
Space Complexity: O(m)
where m
is the maximum value in nums
.
- The
total
array:O(m + 1) = O(m)
- creates an array of sizemx + 1
- Additional variables (
first
,second
,cur
):O(1)
- Overall:
O(m)
Note: In the worst case where m
is significantly larger than n
(e.g., nums = [1, 10000]
), the algorithm becomes inefficient. An alternative approach using a hash map or sorting could achieve O(n log n)
time complexity with O(n)
space complexity, which would be more efficient when m >> n
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Index Out of Bounds When max_value is 0 or 1
The most critical pitfall in this solution is accessing points[1]
when the maximum value in the array is 0. This causes an IndexError.
Problematic Code:
prev = max(points[0], points[1]) # Fails if max_value is 0
Example Case That Breaks:
- Input:
nums = [0, 0, 0]
max_value = 0
, sopoints
array has length 1- Accessing
points[1]
throws IndexError
Solution: Add boundary condition checking:
# After creating points array
if max_value == 0:
return points[0]
prev_prev = points[0]
prev = max(points[0], points[1]) if max_value >= 1 else points[0]
2. Memory Limit Exceeded for Large Sparse Values
When the input contains very large numbers that are sparsely distributed, the solution creates unnecessarily large arrays.
Problematic Scenario:
- Input:
nums = [1, 2, 1000000]
- Creates an array of size 1,000,001 for just 3 unique values
- Wastes memory and time iterating through empty indices
Solution: Use a hash map approach with sorted unique values:
def deleteAndEarn(self, nums: List[int]) -> int:
if not nums:
return 0
# Use Counter to get frequency of each number
from collections import Counter
count = Counter(nums)
unique_nums = sorted(count.keys())
# DP on unique values only
n = len(unique_nums)
dp = [0] * n
dp[0] = unique_nums[0] * count[unique_nums[0]]
for i in range(1, n):
curr_val = unique_nums[i]
curr_points = curr_val * count[curr_val]
# Check if previous unique number is adjacent
if unique_nums[i-1] == curr_val - 1:
# Adjacent: can't take both
take = curr_points + (dp[i-2] if i >= 2 else 0)
skip = dp[i-1]
dp[i] = max(take, skip)
else:
# Not adjacent: can take both
dp[i] = dp[i-1] + curr_points
return dp[n-1]
3. Integer Overflow in Points Calculation
While Python handles big integers automatically, in other languages or when optimizing, the sum calculation points[num] += num
could overflow.
Problematic Case:
- Input:
nums = [100000] * 100000
points[100000]
would be 10,000,000,000 (10 billion)
Solution: Check for potential overflow or use appropriate data types:
# Add overflow awareness (though Python handles this automatically) MAX_SAFE_VALUE = 10**15 # Define reasonable limit if points[num] > MAX_SAFE_VALUE - num: # Handle overflow case raise ValueError("Sum too large") points[num] += num
Which data structure is used to implement recursion?
Recommended Readings
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!