Facebook Pixel

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, earning nums[i] points
  • When you delete nums[i], you must also delete ALL elements with value nums[i] - 1 and ALL elements with value nums[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 all 3s and 5s (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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. Take number i: We get total[i] points plus the best we could do up to i-2 (since we can't take i-1)
  2. Skip number i: We keep the best result up to i-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 index i-2
  • second: Maximum points we can get considering elements up to index i-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 from i-2)
    • second: Skip current number (keep best result from i-1)
  • Update variables for next iteration: first becomes old second, second becomes cur

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 Evaluator

Example 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
  • 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 to mx
  • 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 size mx + 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, so points 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
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which data structure is used to implement recursion?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More