leetcode740 动态规划

题目:

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都可能出现数组越界。
    d p [ i ] [ j ] = { m a x { d p [ i ] [ k − 2 ] + d p [ k + 2 ] [ j ] + k ∗ n c o u n t [ k ] } i < = k < = j , k − 2 > = i , k + 2 < = j m a x { d p [ k + 2 ] [ j ] + k ∗ n c o u n t [ k ] } i < = k < = j , k − 2 < i , k + 2 < = j m a x { d p [ i ] [ k − 2 ] + k ∗ n c o u n t [ k ] } i < = k < = j , k − 2 > = i , k + 2 > j m a x { k ∗ n c o u n t [ 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][k2]+dp[k+2][j]+kncount[k]}max{ dp[k+2][j]+kncount[k]}max{ dp[i][k2]+kncount[k]}max{ kncount[k]}i<=k<=j,k2>=i,k+2<=ji<=k<=j,k2<i,k+2<=ji<=k<=j,k2>=i,k+2>ji<=k<=j,k2<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]中,能得到的最大值。
  • 状态转移方程

d p [ i ] = { m a x { d p [ i − 1 ] , d p [ i − 2 ] + i ∗ n c o u n t [ i ] } m + 2 < = i < = n m a x { d p [ i − 1 ] , i ∗ n c o u n t [ 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[i1],dp[i2]+incount[i]}max{ dp[i1],incount[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];
    }
};

猜你喜欢

转载自blog.csdn.net/sinat_37517996/article/details/104752573