Before attempting this problem, you should be comfortable with:
Dynamic Programming - Understanding how to build solutions from subproblems, similar to House Robber pattern
Hash Maps - Grouping and summing values by key to precompute totals for each unique number
Sorting - Ordering elements to process consecutive values together
1. Recursion
Intuition
When you pick a number x, you earn all points from every occurrence of x, but you must delete all instances of x - 1 and x + 1. This creates a choice at each distinct value: either take it (and skip the next consecutive value) or skip it. Sorting helps group identical values together, making it easy to sum all occurrences. We recursively explore both choices at each group of identical numbers.
Algorithm
Sort the array to group identical numbers together.
Define a recursive function dfs(i) starting at index i:
If i is out of bounds, return 0.
Sum all occurrences of nums[i] and move i past this group.
Compute the result of skipping this group: dfs(new_i).
Skip any numbers equal to nums[i] + 1 (they would be deleted if we pick).
Compute the result of picking this group: pick + dfs(after_skipping).
classSolution:defdeleteAndEarn(self, nums: List[int])->int:
nums.sort()defdfs(i):if i >=len(nums):return0
cur = nums[i]
pick =0while i <len(nums)and nums[i]== cur:
pick += nums[i]
i +=1
res = dfs(i)while i <len(nums)and nums[i]==1+ cur:
i +=1
res =max(res, pick + dfs(i))return res
return dfs(0)
publicclassSolution{publicintdeleteAndEarn(int[] nums){Arrays.sort(nums);returndfs(nums,0);}privateintdfs(int[] nums,int i){if(i >= nums.length)return0;int cur = nums[i], pick =0;while(i < nums.length && nums[i]== cur){
pick += nums[i];
i++;}int res =dfs(nums, i);while(i < nums.length && nums[i]== cur +1){
i++;}
res =Math.max(res, pick +dfs(nums, i));return res;}}
classSolution{public:intdeleteAndEarn(vector<int>& nums){sort(nums.begin(), nums.end());returndfs(nums,0);}private:intdfs(const vector<int>& nums,int i){if(i >= nums.size())return0;int cur = nums[i], pick =0;while(i < nums.size()&& nums[i]== cur){
pick += nums[i];
i++;}int res =dfs(nums, i);while(i < nums.size()&& nums[i]== cur +1){
i++;}
res =max(res, pick +dfs(nums, i));return res;}};
classSolution{/**
* @param {number[]} nums
* @return {number}
*/deleteAndEarn(nums){
nums.sort((a, b)=> a - b);constdfs=(i)=>{if(i >= nums.length)return0;let cur = nums[i],
pick =0;while(i < nums.length && nums[i]=== cur){
pick += nums[i];
i++;}let res =dfs(i);while(i < nums.length && nums[i]=== cur +1){
i++;}
res = Math.max(res, pick +dfs(i));return res;};returndfs(0);}}
funcdeleteAndEarn(nums []int)int{
sort.Ints(nums)var dfs func(i int)int
dfs =func(i int)int{if i >=len(nums){return0}
cur := nums[i]
pick :=0for i <len(nums)&& nums[i]== cur {
pick += nums[i]
i++}
res :=dfs(i)for i <len(nums)&& nums[i]== cur+1{
i++}
res =max(res, pick+dfs(i))return res
}returndfs(0)}funcmax(a, b int)int{if a > b {return a
}return b
}
class Solution {fundeleteAndEarn(nums: IntArray): Int {
nums.sort()returndfs(nums,0)}privatefundfs(nums: IntArray, idx: Int): Int {var i = idx
if(i >= nums.size)return0val cur = nums[i]var pick =0while(i < nums.size && nums[i]== cur){
pick += nums[i]
i++}var res =dfs(nums, i)while(i < nums.size && nums[i]== cur +1){
i++}
res =maxOf(res, pick +dfs(nums, i))return res
}}
classSolution{funcdeleteAndEarn(_ nums:[Int])->Int{let sortedNums = nums.sorted()funcdfs(_ i:Int)->Int{var i = i
if i >= sortedNums.count {return0}let cur = sortedNums[i]var pick =0while i < sortedNums.count && sortedNums[i]== cur {
pick += sortedNums[i]
i +=1}var res =dfs(i)while i < sortedNums.count && sortedNums[i]== cur +1{
i +=1}
res =max(res, pick +dfs(i))return res
}returndfs(0)}}
Time & Space Complexity
Time complexity: O(2n)
Space complexity: O(n)
2. Dynamic Programming (Top-Down)
Intuition
The recursive solution has overlapping subproblems since we may revisit the same index multiple times. By precomputing the total value for each unique number (sum of all occurrences) and using memoization, we avoid redundant calculations. The problem reduces to: for each unique number, decide whether to take it (earning its total value but skipping the next consecutive number) or skip it.
Algorithm
Build a map where each unique number maps to the sum of all its occurrences.
Extract and sort the unique numbers.
Create a memo array initialized to -1.
Define dfs(i):
If i is out of bounds, return 0.
If memo[i] is set, return it.
Compute the value of taking nums[i]: its total value plus dfs(i + 2) if the next number is consecutive, else dfs(i + 1).
classSolution:defdeleteAndEarn(self, nums: List[int])->int:
val = defaultdict(int)for num in nums:
val[num]+= num
nums =sorted(list(set(nums)))
memo =[-1]*len(nums)defdfs(i):if i >=len(nums):return0if memo[i]!=-1:return memo[i]
res = val[nums[i]]if i +1<len(nums)and nums[i]+1== nums[i +1]:
res += dfs(i +2)else:
res += dfs(i +1)
res =max(res, dfs(i +1))
memo[i]= res
return res
return dfs(0)
publicclassSolution{privateMap<Integer,Integer> val;privateint[] memo;publicintdeleteAndEarn(int[] nums){
val =newHashMap<>();for(int num : nums){
val.put(num, val.getOrDefault(num,0)+ num);}List<Integer> uniqueNums =newArrayList<>(val.keySet());Collections.sort(uniqueNums);
memo =newint[uniqueNums.size()];Arrays.fill(memo,-1);returndfs(uniqueNums,0);}privateintdfs(List<Integer> nums,int i){if(i >= nums.size())return0;if(memo[i]!=-1)return memo[i];int res = val.get(nums.get(i));if(i +1< nums.size()&& nums.get(i)+1== nums.get(i +1)){
res +=dfs(nums, i +2);}else{
res +=dfs(nums, i +1);}
res =Math.max(res,dfs(nums, i +1));
memo[i]= res;return res;}}
classSolution{
unordered_map<int,int> val;
vector<int> memo;public:intdeleteAndEarn(vector<int>& nums){for(int num : nums){
val[num]+= num;}
vector<int> uniqueNums;for(auto& pair : val){
uniqueNums.push_back(pair.first);}sort(uniqueNums.begin(), uniqueNums.end());
memo.resize(uniqueNums.size(),-1);returndfs(uniqueNums,0);}private:intdfs(vector<int>& nums,int i){if(i >= nums.size())return0;if(memo[i]!=-1)return memo[i];int res = val[nums[i]];if(i +1< nums.size()&& nums[i]+1== nums[i +1]){
res +=dfs(nums, i +2);}else{
res +=dfs(nums, i +1);}
res =max(res,dfs(nums, i +1));
memo[i]= res;return res;}};
classSolution{/**
* @param {number[]} nums
* @return {number}
*/deleteAndEarn(nums){const val =newMap();
nums.forEach((num)=>{
val.set(num,(val.get(num)||0)+ num);});const uniqueNums = Array.from(newSet(nums)).sort((a, b)=> a - b);const memo =Array(uniqueNums.length).fill(-1);constdfs=(nums, i)=>{if(i >= nums.length)return0;if(memo[i]!==-1)return memo[i];let res = val.get(nums[i]);if(i +1< nums.length && nums[i]+1=== nums[i +1]){
res +=dfs(nums, i +2);}else{
res +=dfs(nums, i +1);}
res = Math.max(res,dfs(nums, i +1));
memo[i]= res;return res;};returndfs(uniqueNums,0);}}
funcdeleteAndEarn(nums []int)int{
val :=make(map[int]int)for_, num :=range nums {
val[num]+= num
}
uniqueSet :=make(map[int]bool)for_, num :=range nums {
uniqueSet[num]=true}
uniqueNums :=make([]int,0,len(uniqueSet))for num :=range uniqueSet {
uniqueNums =append(uniqueNums, num)}
sort.Ints(uniqueNums)
memo :=make([]int,len(uniqueNums))for i :=range memo {
memo[i]=-1}var dfs func(i int)int
dfs =func(i int)int{if i >=len(uniqueNums){return0}if memo[i]!=-1{return memo[i]}
res := val[uniqueNums[i]]if i+1<len(uniqueNums)&& uniqueNums[i]+1== uniqueNums[i+1]{
res +=dfs(i +2)}else{
res +=dfs(i +1)}
res =max(res,dfs(i+1))
memo[i]= res
return res
}returndfs(0)}funcmax(a, b int)int{if a > b {return a
}return b
}
class Solution {privatelateinitvar valMap: MutableMap<Int, Int>privatelateinitvar memo: IntArray
fundeleteAndEarn(nums: IntArray): Int {
valMap =mutableMapOf()for(num in nums){
valMap[num]= valMap.getOrDefault(num,0)+ num
}val uniqueNums = valMap.keys.sorted()
memo =IntArray(uniqueNums.size){-1}returndfs(uniqueNums,0)}privatefundfs(nums: List<Int>, i: Int): Int {if(i >= nums.size)return0if(memo[i]!=-1)return memo[i]var res = valMap[nums[i]]!!
res +=if(i +1< nums.size && nums[i]+1== nums[i +1]){dfs(nums, i +2)}else{dfs(nums, i +1)}
res =maxOf(res,dfs(nums, i +1))
memo[i]= res
return res
}}
classSolution{funcdeleteAndEarn(_ nums:[Int])->Int{var val =[Int:Int]()for num in nums {
val[num,default:0]+= num
}let uniqueNums =Array(Set(nums)).sorted()var memo =[Int](repeating:-1, count: uniqueNums.count)funcdfs(_ i:Int)->Int{if i >= uniqueNums.count {return0}if memo[i]!=-1{return memo[i]}var res = val[uniqueNums[i]]!if i +1< uniqueNums.count && uniqueNums[i]+1== uniqueNums[i +1]{
res +=dfs(i +2)}else{
res +=dfs(i +1)}
res =max(res,dfs(i +1))
memo[i]= res
return res
}returndfs(0)}}
Time & Space Complexity
Time complexity: O(nlogn)
Space complexity: O(n)
3. Dynamic Programming (Bottom-Up) - I
Intuition
We can convert the top-down solution to bottom-up by processing unique numbers from right to left. At each position, we compute the maximum points achievable from that position onward. If the current and next numbers are consecutive, taking the current means skipping the next; otherwise, we can take the current and continue from the next.
Algorithm
Build a map of each unique number to the sum of all its occurrences.
Extract and sort the unique numbers.
Create a DP array of size n + 1 initialized to 0.
Iterate from the last unique number to the first:
Compute take as the value of the current number plus dp[i + 2] if the next is consecutive, else dp[i + 1].
classSolution:defdeleteAndEarn(self, nums: List[int])->int:
val = defaultdict(int)for num in nums:
val[num]+= num
nums =sorted(list(set(nums)))
dp =[0]*(len(nums)+1)for i inrange(len(nums)-1,-1,-1):
take = val[nums[i]]if i +1<len(nums)and nums[i +1]== nums[i]+1:
take += dp[i +2]else:
take += dp[i +1]
dp[i]=max(dp[i +1], take)return dp[0]
publicclassSolution{publicintdeleteAndEarn(int[] nums){Map<Integer,Integer> val =newHashMap<>();for(int num : nums) val.put(num, val.getOrDefault(num,0)+ num);List<Integer> sortedNums =newArrayList<>(val.keySet());Collections.sort(sortedNums);int[] dp =newint[sortedNums.size()+1];for(int i = sortedNums.size()-1; i >=0; i--){int take = val.get(sortedNums.get(i));if(i +1< sortedNums.size()&& sortedNums.get(i +1)== sortedNums.get(i)+1){
take += dp[i +2];}else{
take += dp[i +1];}
dp[i]=Math.max(dp[i +1], take);}return dp[0];}}
classSolution{public:intdeleteAndEarn(vector<int>& nums){
unordered_map<int,int> val;for(int num : nums) val[num]+= num;
vector<int> sortedNums;for(auto&[key, _]: val) sortedNums.push_back(key);sort(sortedNums.begin(), sortedNums.end());
vector<int>dp(sortedNums.size()+1);for(int i = sortedNums.size()-1; i >=0; i--){int take = val[sortedNums[i]];if(i +1< sortedNums.size()&& sortedNums[i +1]== sortedNums[i]+1){
take += dp[i +2];}else{
take += dp[i +1];}
dp[i]=max(dp[i +1], take);}return dp[0];}};
classSolution{/**
* @param {number[]} nums
* @return {number}
*/deleteAndEarn(nums){const val =newMap();
nums.forEach((num)=> val.set(num,(val.get(num)||0)+ num));const sortedNums = Array.from(val.keys()).sort((a, b)=> a - b);const dp =Array(sortedNums.length +1).fill(0);for(let i = sortedNums.length -1; i >=0; i--){let take = val.get(sortedNums[i]);if(
i +1< sortedNums.length &&
sortedNums[i +1]=== sortedNums[i]+1){
take += dp[i +2];}else{
take += dp[i +1];}
dp[i]= Math.max(dp[i +1], take);}return dp[0];}}
funcdeleteAndEarn(nums []int)int{
val :=make(map[int]int)for_, num :=range nums {
val[num]+= num
}
sortedNums :=make([]int,0,len(val))for key :=range val {
sortedNums =append(sortedNums, key)}
sort.Ints(sortedNums)
dp :=make([]int,len(sortedNums)+1)for i :=len(sortedNums)-1; i >=0; i--{
take := val[sortedNums[i]]if i+1<len(sortedNums)&& sortedNums[i+1]== sortedNums[i]+1{
take += dp[i+2]}else{
take += dp[i+1]}
dp[i]=max(dp[i+1], take)}return dp[0]}funcmax(a, b int)int{if a > b {return a
}return b
}
class Solution {fundeleteAndEarn(nums: IntArray): Int {val valMap = mutableMapOf<Int, Int>()for(num in nums){
valMap[num]= valMap.getOrDefault(num,0)+ num
}val sortedNums = valMap.keys.sorted()val dp =IntArray(sortedNums.size +1)for(i in sortedNums.size -1 downTo 0){var take = valMap[sortedNums[i]]!!
take +=if(i +1< sortedNums.size && sortedNums[i +1]== sortedNums[i]+1){
dp[i +2]}else{
dp[i +1]}
dp[i]=maxOf(dp[i +1], take)}return dp[0]}}
classSolution{funcdeleteAndEarn(_ nums:[Int])->Int{var val =[Int:Int]()for num in nums {
val[num,default:0]+= num
}let sortedNums = val.keys.sorted()var dp =[Int](repeating:0, count: sortedNums.count +1)for i instride(from: sortedNums.count -1, through:0, by:-1){var take = val[sortedNums[i]]!if i +1< sortedNums.count && sortedNums[i +1]== sortedNums[i]+1{
take += dp[i +2]}else{
take += dp[i +1]}
dp[i]=max(dp[i +1], take)}return dp[0]}}
Time & Space Complexity
Time complexity: O(nlogn)
Space complexity: O(n)
4. Dynamic Programming (Bottom-Up) - II
Intuition
Instead of working with unique sorted numbers, we can use an array indexed by the numbers themselves (0 to max). Each index stores the total points for that number. This transforms the problem into the classic House Robber problem: you cannot take adjacent indices. We iterate backward, and at each position, choose the maximum of skipping it or taking it plus the result two positions ahead.
Algorithm
Find the maximum value m in the array.
Create a DP array of size m + 2 initialized to 0.
For each number in the input, add it to dp[num] (accumulating total points per number).
classSolution:defdeleteAndEarn(self, nums: List[int])->int:
m =max(nums)
dp =[0]*(m +2)for num in nums:
dp[num]+= num
for i inrange(m -1,0,-1):
dp[i]=max(dp[i +1], dp[i +2]+ dp[i])return dp[1]
publicclassSolution{publicintdeleteAndEarn(int[] nums){int m =0;for(int num : nums) m =Math.max(m, num);int[] dp =newint[m +2];for(int num : nums) dp[num]+= num;for(int i = m -1; i >0; i--){
dp[i]=Math.max(dp[i +1], dp[i +2]+ dp[i]);}return dp[1];}}
classSolution{public:intdeleteAndEarn(vector<int>& nums){int m =*max_element(nums.begin(), nums.end());
vector<int>dp(m +2);for(auto& num : nums){
dp[num]+= num;}for(int i = m -1; i >0; i--){
dp[i]=max(dp[i +1], dp[i +2]+ dp[i]);}return dp[1];}};
classSolution{/**
* @param {number[]} nums
* @return {number}
*/deleteAndEarn(nums){const m = Math.max(...nums);const dp =newInt32Array(m +2);for(let num of nums){
dp[num]+= num;}for(let i = m -1; i >0; i--){
dp[i]= Math.max(dp[i +1], dp[i +2]+ dp[i]);}return dp[1];}}
funcdeleteAndEarn(nums []int)int{
m :=0for_, num :=range nums {if num > m {
m = num
}}
dp :=make([]int, m+2)for_, num :=range nums {
dp[num]+= num
}for i := m -1; i >0; i--{
dp[i]=max(dp[i+1], dp[i+2]+dp[i])}return dp[1]}funcmax(a, b int)int{if a > b {return a
}return b
}
class Solution {fundeleteAndEarn(nums: IntArray): Int {val m = nums.maxOrNull()!!val dp =IntArray(m +2)for(num in nums){
dp[num]+= num
}for(i in m -1 downTo 1){
dp[i]=maxOf(dp[i +1], dp[i +2]+ dp[i])}return dp[1]}}
classSolution{funcdeleteAndEarn(_ nums:[Int])->Int{let m = nums.max()!var dp =[Int](repeating:0, count: m +2)for num in nums {
dp[num]+= num
}for i instride(from: m -1, through:1, by:-1){
dp[i]=max(dp[i +1], dp[i +2]+ dp[i])}return dp[1]}}
Time & Space Complexity
Time complexity: O(m+n)
Space complexity: O(m)
Where m is the maximum element in the array and n is the size of the array.
5. Dynamic Programming (Space Optimized)
Intuition
We only need to track the maximum earnings for the previous two positions, similar to the space-optimized House Robber solution. By iterating through sorted unique numbers and maintaining two variables, we can reduce space complexity. When consecutive numbers differ by more than 1, there is no conflict, so we can add the current earnings directly.
Algorithm
Build a map of each unique number to the sum of all its occurrences.
Sort the unique numbers.
Initialize earn1 = 0 and earn2 = 0 to track the max earnings at the previous two positions.
Iterate through the sorted unique numbers:
If the current number is consecutive to the previous, we have a choice: earn2 = max(curEarn + earn1, earn2).
If not consecutive, we can freely take the current: earn2 = curEarn + earn2.
When you pick a number, you earn points from every occurrence of that number in the array. A common mistake is only counting it once instead of summing all instances.
# Wrong: only counts one occurrence
val[num]= num
# Correct: sum all occurrences
val[num]+= num
Forgetting to Skip Adjacent Values
When you earn points for a number x, you must delete all instances of x-1 and x+1. This means if you pick a value, you cannot pick the consecutive values. Missing this skip logic leads to incorrect results.
# Wrong: doesn't skip consecutive numbers
res = val[nums[i]]+ dfs(i +1)# Correct: skip next if consecutiveif nums[i]+1== nums[i +1]:
res = val[nums[i]]+ dfs(i +2)else:
res = val[nums[i]]+ dfs(i +1)
Treating Non-Consecutive Numbers as Adjacent
In the House Robber transformation, only consecutive numbers conflict. If numbers have gaps (e.g., 2 and 5), both can be taken without any penalty. Treating all numbers as adjacent loses points.
# Wrong: always treats as adjacent
earn2 =max(curEarn + earn1, earn2)# Correct: check if actually consecutiveif i >0and nums[i]== nums[i -1]+1:
earn2 =max(curEarn + earn1, earn2)else:
earn2 = curEarn + earn2 # No conflict, add freely