Delete and Earn
Problem
Given an array of integers nums, return the maximum points you can earn by deleting elements such that deleting a number also deletes all instances of numbers equal to that number minus one and plus one.
- 1 ≤ nums.length ≤ 2 * 10⁴
- 1 ≤ nums[i] ≤ 10⁴
Example
nums = [3, 4, 2]6Choosing 4 earns 4 points and deletes 3 and 5 (if present). The array becomes [2]. Then choosing 2 earns 2 points. Total points = 6. The algorithm first counts the total points for each unique number: 2 -> 2, 3 -> 3, 4 -> 4. It then uses a DP approach similar to the House Robber problem to decide whether to take or skip each number in ascending order. The critical moment is deciding between taking 4 (and skipping 3) or taking 3 and 2. The DP chooses the optimal combination to maximize points.
Approach
Straightforward Solution
A brute-force approach tries all subsets of numbers, deleting neighbors each time, which is exponential and infeasible for large inputs.
Core Observation
The problem reduces to selecting numbers to maximize points where choosing a number excludes its immediate neighbors (number - 1 and number + 1). This is analogous to the House Robber problem where adjacent houses cannot be robbed together.
Path to Optimal
By aggregating points for each unique number (multiplying the number by its frequency), the problem transforms into a linear sequence where each position corresponds to a number and its total points. The adjacency constraint translates to not choosing adjacent indices. This insight allows applying a classic DP approach similar to House Robber, where dp[i] = max(dp[i-1], dp[i-2] + points[i]).
Optimal Approach
Count total points for each number using a frequency map, then iterate from 1 to max number, using DP to decide whether to take or skip each number to maximize total points. This yields an O(n + m) time solution, where n is input size and m is max number value.
Time
O(n + m)
Counting frequencies takes O(n). The DP iterates through all numbers up to max_val (m), performing constant work per number, resulting in O(n + m) total time.
Space
O(m)
Auxiliary space is used for the frequency map and DP arrays, each sized by max_val + 1, which is O(m). This space is necessary to store aggregated points and DP states.
Pattern Spotlight
Dynamic Programming (House Robber Variant)
When a problem requires maximizing sum with a constraint that choosing an element excludes its immediate neighbors, transform the problem into a linear DP where each state represents the best solution up to that element, choosing between skipping or taking it.
Solution
| 1 | class Solution: |
| 2 | def deleteAndEarn(self, nums): |
| 3 | freq = Counter(nums) |
| 4 | max_val = max(nums) |
| 5 | |
| 6 | points = [0] * (max_val + 1) |
| 7 | for v, c in freq.items(): |
| 8 | points[v] = v * c |
| 9 | |
| 10 | dp = [0] * (max_val + 1) |
| 11 | dp[1] = points[1] |
| 12 | |
| 13 | for i in range(2, max_val + 1): |
| 14 | dp[i] = max(dp[i-1], dp[i-2] + points[i]) |
| 15 | |
| 16 | return dp[max_val] |
Step-by-Step Solution
Aggregate Total Points for Each Unique Number Using Frequency Counting
| 3 | freq = Counter(nums) |
| 4 | max_val = max(nums) |
| 6 | points = [0] * (max_val + 1) |
| 7 | for v, c in freq.items(): |
| 8 | points[v] = v * c |
Objective
To compute the total points obtainable from each unique number by multiplying the number by its frequency in the input array.
Key Insight
By counting how many times each number appears, the problem simplifies from dealing with individual elements to dealing with total points per number. This aggregation converts the problem into a linear sequence where each index corresponds to a number and its total points, enabling the DP approach.
Interview Quick-Check
Core Logic
Frequency counting aggregates the total points per number, transforming the problem into a linear DP over these aggregated values.
Common Pitfalls & Bugs
Forgetting to multiply the number by its frequency leads to incorrect total points and suboptimal DP results.
Apply Dynamic Programming to Maximize Points with Adjacency Constraints
| 10 | dp = [0] * (max_val + 1) |
| 11 | dp[1] = points[1] |
| 13 | for i in range(2, max_val + 1): |
| 14 | dp[i] = max(dp[i-1], dp[i-2] + points[i]) |
Objective
To use a DP array where each element represents the maximum points achievable up to that number, deciding whether to take or skip it based on adjacency rules.
Key Insight
The DP recurrence dp[i] = max(dp[i-1], dp[i-2] + points[i]) captures the choice between skipping the current number (taking dp[i-1]) or taking it and adding its points to dp[i-2]. This mirrors the House Robber problem and ensures no two adjacent numbers are chosen, respecting the problem's deletion constraints.
Interview Quick-Check
Core Logic
The DP recurrence balances the trade-off between taking the current number's points plus dp[i-2] or skipping it to keep dp[i-1], ensuring no adjacent numbers are chosen.
State & Boundaries
Initialize dp[1] with points[1] as the base case, and iterate from 2 to max_val to build up the solution.
Common Pitfalls & Bugs
Incorrectly initializing dp or misapplying the recurrence can lead to off-by-one errors or invalid adjacency handling.
Return the Maximum Points Achievable After Processing All Numbers
| 16 | return dp[max_val] |
Objective
To output the final computed maximum points from the DP array corresponding to the largest number in the input.
Key Insight
The DP array accumulates the optimal solutions for all numbers up to max_val, so dp[max_val] holds the maximum points achievable considering all numbers and adjacency constraints.
Interview Quick-Check
Core Logic
Returning dp[max_val] provides the final answer after considering all numbers and their adjacency constraints.
Line Analysis
This solution has 2 Critical lines interviewers watch for.
dp[i] = max(dp[i-1], dp[i-2] + points[i])
Compute the optimal score for value i using the DP recurrence.
This recurrence encodes the fundamental decision of the problem. Taking value i yields points[i] but prevents selecting i−1, so the best compatible previous result is dp[i−2]. Skipping value i preserves dp[i−1]. Choosing the maximum of these options guarantees that adjacent values are never selected simultaneously while maximizing total reward.
freq = Counter(nums)
Count how many times each number appears in nums.
The original array must be reorganized by value rather than position because the deletion rule depends on numerical adjacency (x removes x−1 and x+1). Aggregating frequencies converts scattered duplicates into a single total reward per value, which is necessary to reduce the problem into a one-dimensional dynamic programming structure.
points = [0] * (max_val + 1)
Create an array where index i stores the total reward obtainable from value i.
The points array converts the frequency information into total reward per value. Each index now represents a decision point: taking value i yields points[i] but forbids taking i−1 and i+1. This transformation reshapes the problem into an adjacency-constrained selection problem.
points[v] = v * c
Compute the total points contributed by each value.
If the algorithm chooses value v, it will take every occurrence of v because duplicates do not conflict with each other. Multiplying the value by its frequency correctly aggregates the full reward obtainable from that choice.
dp = [0] * (max_val + 1)
Initialize the DP array that will store optimal scores for each value prefix.
The DP array stores optimal results for progressively larger value ranges. Specifically, dp[i] represents the maximum points achievable using values from 0 through i while respecting the adjacency restriction.
return dp[max_val]
Return the optimal total points.
After processing all values up to max_val, dp[max_val] contains the best achievable reward under the adjacency constraint. This value represents the optimal solution for the entire problem.
max_val = max(nums)
Determine the largest value in nums to size the value-indexed arrays.
The dynamic programming process iterates across possible numeric values rather than array positions. Determining the largest value establishes the upper bound of the value domain so that the DP and reward arrays cover every possible number that could influence the optimal solution.
dp[1] = points[1]
Establish the base case for value 1.
The recurrence depends on dp[i−1] and dp[i−2], so the smallest meaningful state must be established first. Setting dp[1] anchors the DP computation and ensures later transitions have valid base values.
for v, c in freq.items():
Iterate through each unique value and its frequency.
Iterating through the frequency map populates the reward structure used by the DP. This step compresses all duplicates of the same number into a single aggregated decision, which simplifies later transitions.
for i in range(2, max_val + 1):
Iterate through each value from 2 up to max_val.
Processing values sequentially ensures that when computing dp[i], both dp[i−1] and dp[i−2] have already been determined. This ordering preserves the dependency structure of the recurrence.
Test Your Understanding
Why does aggregating points by number and applying a House Robber style DP solve the problem optimally?
Because choosing a number removes its neighbors, the problem reduces to selecting non-adjacent numbers to maximize sum. Aggregating points per number creates a linear structure where the adjacency constraint applies to indices, allowing DP to optimally decide to take or skip each number.
Related Problems
Dynamic Programming pattern
Don't just read it. Drill it.
Reconstruct Delete and Earn from memory until it sticks. AlgoDrill blanks out key lines and makes you fill them back in, step by step.
Unlock the Delete and Earn drill