问题描述
给定一个整数数组 nums ,你可以对它进行一些操作。
每次操作中,选择任意一个 nums[i] ,删除它并获得 nums[i] 的点数。之后,你必须删除每个等于 nums[i] - 1 或 nums[i] + 1 的元素。
开始你拥有 0 个点数。返回你能通过这些操作获得的最大点数。
解题报告
一个数删或者不删意味着拿或者不拿,当拿了这个数之后,会限制另外一些数不能拿。
我们不妨从最粗暴的方式开始。
设dp[i][0]表示第i个数不拿能获得的最大点数;dp[i][1]表示第i个数拿能获得的最大点数。这里面有两种方式创建状态方程:
- 一个是以数组下标作为索引;
- 一个是以数组中的值作为索引;
以数组下标作为索引
为了便于写转移方程,我们不妨先将数组排序。
-
当第 i 个数拿时:
如果第 i 个数和第 i-1 个数相同,那么 d p [ i ] [ 1 ] = d p [ i − 1 ] [ 1 ] + n u m s [ i ] dp[i][1]=dp[i-1][1]+nums[i] dp[i][1]=dp[i−1][1]+nums[i];
如果第 i 个数和第 i-1 个数不同,那么我们要考虑第 i 个数是否比第 i-1 个数大 1,若是,则转移方程为 d p [ i ] [ 1 ] = d p [ i − 1 ] [ 0 ] + n u m s [ i ] dp[i][1]=dp[i-1][0]+nums[i] dp[i][1]=dp[i−1][0]+nums[i];若不是,则转移方程为 d p [ i ] [ 1 ] = m a x ( d p [ i − 1 ] [ 1 ] , d p [ i − 1 ] [ 0 ] ) + n u m s [ i ] dp[i][1]=max(dp[i-1][1],dp[i-1][0])+nums[i] dp[i][1]=max(dp[i−1][1],dp[i−1][0])+nums[i]; -
当第 i 个数不拿时:
如果第 i 个数和第 i-1 个数相同,那么 d p [ i ] [ 0 ] = d p [ i − 1 ] [ 0 ] dp[i][0]=dp[i-1][0] dp[i][0]=dp[i−1][0];
如果第 i 个数和第 i-1 个数不同,那么 d p [ i ] [ 0 ] = m a x ( d p [ i − 1 ] [ 1 ] , d p [ i − 1 ] [ 0 ] ) dp[i][0]=max(dp[i-1][1],dp[i-1][0]) dp[i][0]=max(dp[i−1][1],dp[i−1][0]);
时间复杂度
O
(
n
⋅
l
o
g
n
)
O(n\cdot logn)
O(n⋅logn)
空间复杂度
O
(
n
)
O(n)
O(n)
以数组中的值作为索引
以数组中的值作为索引时,第i个数和第i-1个数具有天然的+1性能,所以可以省去很多判断。
转移方程为:
d
p
[
i
]
[
1
]
=
d
p
[
i
−
1
]
[
0
]
+
i
∗
c
o
u
n
t
(
i
)
dp[i][1]=dp[i-1][0]+i*count(i)
dp[i][1]=dp[i−1][0]+i∗count(i)
d
p
[
i
]
[
0
]
=
m
a
x
(
d
p
[
i
−
1
]
[
0
]
,
d
p
[
i
−
1
]
[
1
]
)
dp[i][0]=max(dp[i-1][0],dp[i-1][1])
dp[i][0]=max(dp[i−1][0],dp[i−1][1])
时间复杂度
O
(
m
a
x
(
n
u
m
[
i
]
)
)
O(max(num[i]))
O(max(num[i]))
空间复杂度
O
(
m
a
x
(
n
u
m
[
i
]
)
)
O(max(num[i]))
O(max(num[i]))
这样的转移方程还有很多优化空间,具体见:Leetcode 198. 打家劫舍【动态规划,一步步从最粗暴的动态规划到最优化滚动数组实现】
实现代码
以数组下标作为索引实现
class Solution {
public:
int deleteAndEarn(vector<int>& nums) {
int n= nums.size();
if(n==0) return 0;
vector<vector<int>>dp(n,vector<int>(2,0));
sort(nums.begin(), nums.end());
dp[0][0]=0;
dp[0][1]=nums[0];
for(int i=1;i<n;i++){
if(nums[i]-nums[i-1]==0){
dp[i][0]=dp[i-1][0];
dp[i][1]=dp[i-1][1]+nums[i];
continue;
}
if(nums[i]-nums[i-1]==1){
dp[i][1]=dp[i-1][0]+nums[i];
}
else{
dp[i][1]=max(dp[i-1][1],dp[i-1][0])+nums[i];
}
dp[i][0]=max(dp[i-1][1],dp[i-1][0]);
}
return max(dp[n-1][0],dp[n-1][1]);
}
};
以数组中的值作为索引实现
class Solution {
public:
int deleteAndEarn(vector<int>& nums) {
if (nums.empty()) return 0;
int N = *max_element(nums.begin(), nums.end());
vector<int> counts(N + 1, 0);
for (auto x : nums) {
++counts[x];
}
vector<vector<int>> dp(N + 1, vector<int>(2,0));
dp[1][0] = 0;
dp[1][1]=counts[1];
for (int i = 2; i <= N; ++i) {
dp[i][0]=max(dp[i-1][1],dp[i-1][0]);
dp[i][1]=dp[i-1][0]+i*counts[i];
}
return max(dp[N][0],dp[N][1]);
}
};
总结
这种是以数组中的值作为索引还是以数组下标作为索引,取决于数组的大小和数组中最大值的大小关系。从这道题的所给数据范围来看【nums的长度最大为20000。每个整数nums[i]的大小都在[1, 10000]范围内】,明显是以数组中的值作为索引的时间复杂度和空间复杂度来的小。
参考资料
[1] Leetcode 740. 删除与获得点数
[2] Leetcode 198. 打家劫舍【动态规划,一步步从最粗暴的动态规划到最优化滚动数组实现】