动态规划(八) 练习题

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& nums) {
        int n = nums.size();
        if(n==0) return 0;
        if(n==1) return nums[0];

        vector> dp(n+1,vector(2,0));
        
        for(int i=2;i

2)

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

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

class Solution {
public:
    int maxSubArray(vector& nums) {
        int n = nums.size();
        vector 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& 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;
    }
};

结果:

动态规划(八) 练习题_第1张图片

 

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 dp(n+1,0);
        dp[1] = 1;
        for(int i=2;i<=n;i++){
            for(int j=1;j

动态规划(八) 练习题_第2张图片

 

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> dp(m+1,vector(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>& pairs) {
        if(pairs.size()==0 || pairs[0].size()==0){
            return 0;
        }
   
        sort(pairs.begin(), pairs.end(),
        [](const vector& a, const vector& b){
            return a[1]> dp(n,vector(n,1));
        int res = 1;

        for(int i=0;i

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

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

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

        for(int i=0;i

6)

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

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

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

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

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

class Solution {
public:
    int wiggleMaxLength(vector& nums) {
        vector nums2;
        int n = nums.size();
        
        nums2.push_back(nums[0]);
        for(int i=1;i dp(n,0);
        dp[0] = 1;
        dp[1] = 2;
        for(int i=2;i

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

class Solution {
public:
    int wiggleMaxLength(vector& nums) {
        vector nums2;
        int n = nums.size();
        
        nums2.push_back(nums[0]);
        for(int i=1;i dp(n,0);
        int pre = 2, cur;
        for(int i=2;i

结果:

动态规划(八) 练习题_第3张图片

 

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& nums, int target) {
        int n = nums.size();
        int len = max(target,-target);
        len = max(len,accumulate(nums.begin(), nums.end(),1));
        vector> dp(n,vector(2*len+1,0));
        
        dp[0][nums[0]+len] += 1;
        dp[0][-nums[0]+len] += 1;
        
        for(int i=1;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& nums, int target) {
        int n = nums.size();
        int len = max(target,-target);
        len = max(len,accumulate(nums.begin(), nums.end(),1));
        vector pre(2*len+1,0);
        vector cur(2*len+1,0);
        
        pre[nums[0]+len] += 1;
        pre[-nums[0]+len] += 1;
        
        for(int i=1;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& prices, int fee) {
        int n = prices.size();
        
        vector dp1(n,0);
        vector dp2(n,0);

        dp1[0] = -prices[0];
        for(int i=1;i

你可能感兴趣的:(leetcode(c++),动态规划,算法,C++)