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

Input: nums = [3, 4, 2]
Output: 6

Choosing 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

1class 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

1

Aggregate Total Points for Each Unique Number Using Frequency Counting

3freq = Counter(nums)
4max_val = max(nums)
6points = [0] * (max_val + 1)
7for 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.

2

Apply Dynamic Programming to Maximize Points with Adjacency Constraints

10dp = [0] * (max_val + 1)
11dp[1] = points[1]
13for 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.

3

Return the Maximum Points Achievable After Processing All Numbers

16return 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.

Line 14 Critical
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.

Line 3 Critical
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.

Line 6 Core
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.

Line 8 Core
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.

Line 10 Core
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.

Line 16 Core
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.

Line 4 Core
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.

Line 11 Core
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.

Line 7 Standard
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.

Line 13 Standard
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

or drill a free problem