动态规划(八) 练习题

1.练习题

1)

力扣https://leetcode.cn/problems/house-robber-ii/解答:

这道题的重点是第一个屋子和最后的一个屋子不能一起偷。

所以可以把屋子分成两组,一组是从第一个屋子到倒数第二个屋子,另一组是从第二个屋子到最后屋子。

针对每组:

dp[i][0]表示不偷第i个屋子时的最大收益,那前一个屋子就是可偷可不偷,取二者中的最大值

dp[i][0] = max(dp[i-1][0], dp[i-1][1])

dp[i][1]表示偷第i个屋子时的最大收益,所以前一个屋子不能偷

dp[i][1] = dp[i-1][1] + nums[i-1]

代码:

class Solution {
public:
    int rob(vector<int>& nums) {
        int n = nums.size();
        if(n==0) return 0;
        if(n==1) return nums[0];

        vector<vector<int>> dp(n+1,vector<int>(2,0));
        
        for(int i=2;i<n+1;i++){
            dp[i][0] = max(dp[i-1][0], dp[i-1][1]);
            dp[i][1] = dp[i-1][0] + nums[i-1];
        }
        int res = max(dp[n][0], dp[n][1]);


        for(int i=1;i<n;i++){
            dp[i][0] = max(dp[i-1][0], dp[i-1][1]);
            dp[i][1] = dp[i-1][0] + nums[i-1];
        }
        int res2 = max(dp[n-1][0], dp[n-1][1]);

        return max(res, res2);

    }
};

2)

力扣icon-default.png?t=N6B9https://leetcode.cn/problems/maximum-subarray/

这题是典型的DP,针对每个元素,考虑是与前面的元素组成连续子数组,或者是放弃前面的元素,当前元素作为连续子数组的开头元素。判断依据就是哪种情况下连续子数组的和更大。

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int n = nums.size();
        vector<int> dp(n+1,INT_MIN/2);
        for(int i=1;i<=n;i++){
            dp[i] = max(dp[i-1]+nums[i-1], nums[i-1]);
           
        }
        return *max_element(dp.begin(), dp.end());
    }
};

可以看到dp[i]只与dp[i-1]相关,所以可以把一维数组压缩成常量。

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int n = nums.size();
        int pre = 0, cur, res = nums[0];
        for(int i=1;i<=n;i++){
            cur = max(pre+nums[i-1], nums[i-1]);
            pre = cur;
            res = max(res, cur);
        }
        return res;
    }
};

结果:

 

3)

力扣icon-default.png?t=N6B9https://leetcode.cn/problems/integer-break/看到这道题首先的思路是dp[i]代表i拆分后的最大乘积,所以可以遍历和为n的所有两个数的组合:

  • 若拆分成两个数:dp[i]=max(dp[i],j*(i-j))
  • 若拆分成两个以上的数:dp[i]=max(dp[i],j*dp[i-j])

这里的dp[i-j]代表i-j这个数再拆分后的最大乘积。

这时候,直觉地会有一个写法dp[i]=max(dp[i],dp[j]*(i-j)),这其实是和第二种情况重复了,i和i-j本质上是等价的。

还有另一个写法dp[i]=max(dp[i],dp[j]*dp[i-j]),我感觉这可以算是第三种情况,就是j和(i-j)都取拆分。但看最后结果,加不加这种情况,不会影响最终结果。大概是因为当把数字拆的过于小了之后,得到的自然不会是最大乘积了。

class Solution {
public:
    int integerBreak(int n) {
        vector<int> dp(n+1,0);
        dp[1] = 1;
        for(int i=2;i<=n;i++){
            for(int j=1;j<i;j++){
                dp[i]=max(dp[i],j*(i-j));
                dp[i]=max(dp[i], j*dp[i-j]);
            }
        }
        return dp[n];
    }
};

 

4)

力扣icon-default.png?t=N6B9https://leetcode.cn/problems/delete-operation-for-two-strings/

这题比之前做的那道字符串编辑的题简单一些,因为这道题只考虑删除。

那么转移方程就很简单了,如果匹配,那最小步数等于前一个字符的情况。

如果不匹配:要么删除字符串1的当前字符,要么删除字符串2的当前字符,要么都删除。

class Solution {
public:
    int minDistance(string word1, string word2) {
        int m = word1.size(), n = word2.size();

        vector<vector<int>> dp(m+1,vector<int>(n+1,0));
        for(int i=0;i<=n;i++){
            dp[0][i]=i;
        }
        for(int i=0;i<=m;i++){
            dp[i][0]=i;
        }
        for(int i=1;i<=m;i++){
            for(int j=1;j<=n;j++){
                if(word1[i-1]==word2[j-1]){
                    dp[i][j] = dp[i-1][j-1];
                }else{
                    dp[i][j] = min(dp[i-1][j]+1, min(dp[i][j-1]+1, dp[i-1][j-1]+2));
                }
            }
        }
        return dp[m][n];
    }
};

5)

力扣icon-default.png?t=N6B9https://leetcode.cn/problems/maximum-length-of-pair-chain/这题最初,我是这么想的,一个二维数组,每一行表示以该数对结尾的数对链(表示之前的数对都已考虑过,可以不取某个数对),每一列表示要新增的数对。

然后二维数组遍历,来判断合法的数对链。

于是我写出了这一段代码:

class Solution {
public:
    int findLongestChain(vector<vector<int>>& pairs) {
        if(pairs.size()==0 || pairs[0].size()==0){
            return 0;
        }
   
        sort(pairs.begin(), pairs.end(),
        [](const vector<int>& a, const vector<int>& b){
            return a[1]<b[1];
        }
        );
      

        int n = pairs.size();
        vector<vector<int>> dp(n,vector<int>(n,1));
        int res = 1;

        for(int i=0;i<n;i++){
            for(int j=i+1;j<n;j++){
                    if(pairs[i][1]<pairs[j][0]){       
                        if(i==0){
                            dp[i][j] += 1;           
                        }else{
                            dp[i][j] = dp[i-1][i]+1;               
                        }
                    }
                    if(i!=0)
                        dp[i][j] = max(dp[i][j], dp[i-1][j]);
                    res = max(res, dp[i][j]);
                
            }
        }
       
      
        return res;

    }
};

写完之后,虽然通过了所有case,但很显然我是想复杂了。这个二维数组左下边的一半都是用不到的,每次转移方程也都是只用到上一次以i或者以j结尾的最长数对链。

所有很明显可以压缩成一维数组,每个元素表示以i结尾时的最长数对链:

class Solution {
public:
    int findLongestChain(vector<vector<int>>& pairs) {
        if(pairs.size()==0 || pairs[0].size()==0){
            return 0;
        }
   
        sort(pairs.begin(), pairs.end(),
        [](const vector<int>& a, const vector<int>& b){
            return a[1]<b[1];
        }
        );
      

        int n = pairs.size();
        vector<int> dp(n,1);
        int res = 1;

        for(int i=0;i<n;i++){
            for(int j=0;j<i;j++){         
                if(pairs[j][1]<pairs[i][0]){
                     dp[i] = max(dp[i], dp[j]+1);  
                     res = max(res, dp[i]);
                }
                   
            }
        }
       
      
        return res;

    }
};

6)

力扣icon-default.png?t=N6B9https://leetcode.cn/problems/wiggle-subsequence/解题思路:

首先如果相邻元素相等,那是肯定不满足要求的,所以删去重复的相邻元素。

接下来再处理,就不会出现差值为零的情况了,只会大于0或者小于0。

所以对连续的三个数的两个差值相乘,来判断是否是摆动的。

如果小于零,说明是摆动的,序列长度加一。否则序列长度不变。

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        vector<int> nums2;
        int n = nums.size();
        
        nums2.push_back(nums[0]);
        for(int i=1;i<n;i++){
            if(nums[i]!=nums[i-1]){
                nums2.push_back(nums[i]);
            }
        }

        n = nums2.size();
        if(n<3) return n;
        vector<int> dp(n,0);
        dp[0] = 1;
        dp[1] = 2;
        for(int i=2;i<n;i++){
            if((nums2[i]-nums2[i-1])*(nums2[i-1]-nums2[i-2])<0){
                dp[i] = dp[i-1] + 1;
            }else{
                dp[i] = dp[i-1];
            }
        }
        return dp[n-1];
    }
};

因为dp[i]只与dp[i-1]相关,所以可以用两个遍历代替一维数组

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        vector<int> nums2;
        int n = nums.size();
        
        nums2.push_back(nums[0]);
        for(int i=1;i<n;i++){
            if(nums[i]!=nums[i-1]){
                nums2.push_back(nums[i]);
            }
        }

        n = nums2.size();
        if(n<3) return n;
        vector<int> dp(n,0);
        int pre = 2, cur;
        for(int i=2;i<n;i++){
            if((nums2[i]-nums2[i-1])*(nums2[i-1]-nums2[i-2])<0){
                cur = pre + 1;
            }else{
                cur = pre;
            }
            pre = cur;
        }
        return cur;
    }
};

结果:

 

7)

力扣icon-default.png?t=N6B9https://leetcode.cn/problems/target-sum/解题思路:用一个二维数组来表示状态转移,横坐标i表示考虑数组[0...i]的组合,纵坐标j表示这些组合的和。

因为组合的和存在负数,所以要加上一个数组列数的偏移量

dp[i][j]表示数组[0...i]的组合和为j,所以等于数组组[0...i-1]的组合和为j-nums[i],加上数组组[0...i-1]的组合和为j+nums[i]的和。当然前提是坐标要不越界。

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int n = nums.size();
        int len = max(target,-target);
        len = max(len,accumulate(nums.begin(), nums.end(),1));
        vector<vector<int>> dp(n,vector<int>(2*len+1,0));
        
        dp[0][nums[0]+len] += 1;
        dp[0][-nums[0]+len] += 1;
        
        for(int i=1;i<n;i++){
            for(int j=0;j<=2*len;j++){
                if((j-nums[i])>=0 && (j-nums[i])<=2*len)
                dp[i][j] += dp[i-1][j-nums[i]];
               if((j+nums[i])>=0 && (j+nums[i])<=2*len)
                dp[i][j] += dp[i-1][j+nums[i]];
            }
        }
       
        return dp[n-1][target+len];
    }
};

因为dp[i]只和dp[i-1]相关,所以压缩成一维数组,又因为遍历数组时,会覆盖掉后面的元素要用到的值,所以最后用两个一维数组来实现:

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int n = nums.size();
        int len = max(target,-target);
        len = max(len,accumulate(nums.begin(), nums.end(),1));
        vector<int> pre(2*len+1,0);
        vector<int> cur(2*len+1,0);
        
        pre[nums[0]+len] += 1;
        pre[-nums[0]+len] += 1;
        
        for(int i=1;i<n;i++){
            for(int j=0;j<=2*len;j++){
                cur[j] = 0;
               if((j-nums[i])>=0 && (j-nums[i])<=2*len)
                cur[j] += pre[j-nums[i]];
               if((j+nums[i])>=0 && (j+nums[i])<=2*len)
                cur[j] += pre[j+nums[i]];
              
            }
         
           pre = cur;
        }
        
       
        return pre[target+len];
    }
};

8)

力扣icon-default.png?t=N6B9https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/解题思路:考虑手上有股票和没有股票的两种情况

有股票:之前购买的,和当前购买的,这两种情况的最大值

没有股票:直接手上就没有股票,和当前卖掉股票,这两种情况的最大值

转移方程:

dp1[i] = max(dp1[i-1], dp2[i-1]-prices[i]);

dp2[i] = max(dp2[i-1], dp1[i-1]+prices[i]-fee);

代码:

class Solution {
public:
    int maxProfit(vector<int>& prices, int fee) {
        int n = prices.size();
        
        vector<int> dp1(n,0);
        vector<int> dp2(n,0);

        dp1[0] = -prices[0];
        for(int i=1;i<n;i++){
            dp1[i] = max(dp1[i-1], dp2[i-1]-prices[i]);
            dp2[i] = max(dp2[i-1], dp1[i-1]+prices[i]-fee);
        }

        return dp2[n-1];
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值