题目:
Given an array nums of integers, you can perform operations on the array.
In each operation, you pick any nums[i]and delete it to earn nums[i]points. After, you must delete every element equal to nums[i] - 1or nums[i] + 1.
You start with 0 points. Return the maximum number of points you can earn by applying such operations.
解法1:复杂版动态规划
三个相邻数字只能留1个,价值为其个数乘以其数值。
以连续的区间为单位处理,各个连续区间的答案简单相加即为整体答案。
一个连续区间,在选择1个以后,就会分裂为两个连续区间,进入递归处理。具体选哪一个没有好策略,因此这里可以用动态规划筛选。
第一步,判断给定nums有哪几个连续区间。
第二步,分别用动态规划求这些连续区间的解。
第三步,所有区间结果相加即为答案。
- 选择状态
dp[i][j]表示在连续闭区间[i,j]上选数(各个数字的个数由数组ncount[]存储)所能得到的最大值。
i = nums[k]; j = nums[t];在nums中,从k号位到t号位数字是连续的。 - 状态转移方程
注意k-2和k+2都可能出现数组越界。
dp[i][j]={max{dp[i][k−2]+dp[k+2][j]+k∗ncount[k]}i<=k<=j,k−2>=i,k+2<=jmax{dp[k+2][j]+k∗ncount[k]}i<=k<=j,k−2<i,k+2<=jmax{dp[i][k−2]+k∗ncount[k]}i<=k<=j,k−2>=i,k+2>jmax{k∗ncount[k]}i<=k<=j,k−2<i,k+2>j dp[i][j] = \begin{cases} max \lbrace dp[i][k-2]+dp[k+2][j]+k*ncount[k] \rbrace & i<=k<=j,k-2>=i,k+2<=j \\ max \lbrace dp[k+2][j]+k*ncount[k] \rbrace & i<=k<=j,k-2<i,k+2<=j \\ max \lbrace dp[i][k-2]+k*ncount[k] \rbrace & i<=k<=j,k-2>=i,k+2>j \\ max \lbrace k*ncount[k] \rbrace & i<=k<=j,k-2<i,k+2>j \end{cases} dp[i][j]=⎩⎪⎪⎪⎨⎪⎪⎪⎧max{dp[i][k−2]+dp[k+2][j]+k∗ncount[k]}max{dp[k+2][j]+k∗ncount[k]}max{dp[i][k−2]+k∗ncount[k]}max{k∗ncount[k]}i<=k<=j,k−2>=i,k+2<=ji<=k<=j,k−2<i,k+2<=ji<=k<=j,k−2>=i,k+2>ji<=k<=j,k−2<i,k+2>j - 边界
dp[i][i] = i*n_count[i] - 处理顺序
nums中出现的最大数字为n,最小数字为m
| i / j | m | m+1 | … | n |
|---|---|---|---|---|
| m | init() | |||
| m+1 | 0 | init() | ||
| … | … | … | … | |
| n | 0 | 0 | 0 | init() |
涉及到的点在当前点的左方或下方,因此处理顺序为从左上到右下。
class Solution {
public:
int deleteAndEarn(vector<int>& nums) {
//统计数值出现的次数,寻找nums的连续区间
if(nums.empty()) return 0;
const int maxn = 10001;
static int ncount[maxn]={};
sort(nums.begin(), nums.end());
vector<int> range;
range.emplace_back(nums[0]);
int last = nums[0]; //上一个访问的数值
int n=0, m=maxn; //出现的最大,最小的数值
for(auto x:nums){
ncount[x]++;
n = max(x, n);
m = min(x, m);
if(x-last>1){
//如果间断了
range.emplace_back(last);
range.emplace_back(x);
}
last = x;
}
range.emplace_back(nums.back());
//动态规划
static int dp[maxn][maxn]={};
for(int i=m; i<=n; i++){
dp[i][i] = i * ncount[i];
}
for(int l=1; l<=n-m; l++){
for(int i=m; i<n; i++){
int j = i + l;
if(j<=n){
for(int k=i; k<=j; k++){
int point;
if(k-2>=i && k+2<=j) point = dp[i][k-2]+dp[k+2][j]+k*ncount[k];
else if(k-2>=i) point = dp[i][k-2]+k*ncount[k];
else if(k+2<=j) point = dp[k+2][j]+k*ncount[k];
else point = k*ncount[k];
if(point>dp[i][j]) dp[i][j] = point;
}
}
}
}
//组合各区间的答案
int t=range.size(), ans=0;
for(int i=0; i<t; i+=2){
ans += dp[range[i]][range[i+1]];
}
return ans;
}
};
结果超时,答案是对的。
还有更简单的动态规划方案。
解法2:动态规划
先把nums从小到大排,问题规模为数字区间的上下限。 设数字最小值为m,最大值为n。
缩小规模:从后往前考察,先判断当前数字n删或不删,若删则判断闭区间[m, n-2],若不删则判断[m, n-1],达到缩小规模。
还原规模:
- 已知闭区间[m, n-2]的最大值,可以得到不删n-1时[m, n-1]的最大值,和删n时[m, n]的最大值。
- 已知闭区间[m, n-1]的最大值,可以得到不删n时[m, n]的最大值。
综上,可以通过当前数字删或不删,不同程度地缩小问题规模到[m, n-2]和[m, n-1]。
- 选择状态
dp[i]表示闭区间[m, i]中,能得到的最大值。 - 状态转移方程
dp[i]={max{dp[i−1],dp[i−2]+i∗ncount[i]}m+2<=i<=nmax{dp[i−1],i∗ncount[i]}i=m+1 dp[i] = \begin{cases} max \lbrace dp[i-1], dp[i-2]+i*ncount[i] \rbrace & m+2<=i<=n \\ max \lbrace dp[i-1], i*ncount[i] \rbrace & i=m+1 \end{cases} dp[i]={max{dp[i−1],dp[i−2]+i∗ncount[i]}max{dp[i−1],i∗ncount[i]}m+2<=i<=ni=m+1
其中ncount[i]表示数字i在nums中出现的次数。
- 边界
dp[m] = m*ncount[m] - 处理顺序
从左到右,直到dp[n]。
犯错点:并不是只有连续区间才可以处理,不连续的区间也可以处理
class Solution {
public:
int deleteAndEarn(vector<int>& nums) {
if(nums.empty()) return 0;
const int maxn = 10001;
int m=maxn, n=0;
int ncount[maxn]={};
for(auto x:nums){
m = min(m, x);
n = max(n, x);
ncount[x]++;
}
int dp[n+1];
dp[m] = m * ncount[m];
for(int i=m+1; i<=n; i++){
if(i==m+1) dp[i] = max(dp[i-1], i*ncount[i]);
else dp[i] = max(dp[i-1], dp[i-2]+i*ncount[i]);
}
return dp[n];
}
};
本文探讨了通过动态规划解决特定数组操作问题的方法,提出了两种动态规划策略:一种是复杂版,采用递归方式处理连续区间;另一种是简化版,通过从前向后考察元素来逐步缩小问题规模,实现高效求解。

336

被折叠的 条评论
为什么被折叠?



