【leetcode刷题】动态规划 Part 1 入门DP

最近刷刷leetcode

主要还是DP这块基础不是很牢,当然难题也做不出来,所以照着灵神的题单一题题刷下去

213:打家劫舍(2)

给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。

示例 1:

输入:nums = [2,3,2]
输出:3
解释:你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。

示例 2:

输入:nums = [1,2,3,1]
输出:4
解释:你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。

示例 3:

输入:nums = [1,2,3]
输出:3

提示:

  • 1 <= nums.length <= 100
  • 0 <= nums[i] <= 1000

Solution

也许是之前的断环成链区间DP做多了,这道题竟然不知道怎么处理
我们分类讨论,如果选择\(nums[0]\),那么\(nums[1]\)\(nums[n-1]\)就无法被选择,再对\(nums[2]\)\(nums[n-2]\)做一次打家劫舍就可以;如果不选则\(nums[0]\),那么对\(nums[1]\)\(nums[n-1]\)做一次打家劫舍,最后直接取最大值

class Solution {
public:
    int rob(vector<int>& nums) {
        int l=nums.size();
        vector<int>dp(l+5,0);
        vector<int>fdp(l+5,0);
        if(l==1) return nums[0];
        if(l==2) return max(nums[0],nums[1]);
        dp[0]=nums[0];
        dp[1]=nums[0];
        for(int i=2;i<=l-2;i++){
            dp[i]=max(dp[i],max(dp[i-1],dp[i-2]+nums[i]));
        }
        fdp[1]=nums[1];
        fdp[2]=max(nums[1],nums[2]);
        for(int i=3;i<=l-1;i++){
            fdp[i]=max(fdp[i],max(fdp[i-1],fdp[i-2]+nums[i]));
        }
        int ans=max(max(dp[l-2],dp[l-3]),max(fdp[l-1],fdp[l-2]));
        return ans;
    }
};

2140:解决智力问题

这个数组表示一场考试里的一系列题目,你需要 按顺序 (也就是从问题 0 开始依次解决),针对每个问题选择 解决 或者 跳过 操作。解决问题 i 将让你 获得 pointsi 的分数,但是你将 无法 解决接下来的 brainpoweri 个问题(即只能跳过接下来的 brainpoweri 个问题)。如果你跳过问题 i ,你可以对下一个问题决定使用哪种操作。

  • 比方说,给你 questions = [ [3, 2], [4, 3], [4, 4], [2, 5] ] :
    • 如果问题 0 被解决了, 那么你可以获得 3 分,但你不能解决问题 1 和 2 。
    • 如果你跳过问题 0 ,且解决问题 1 ,你将获得 4 分但是不能解决问题 2 和 3 。

请你返回这场考试里你能获得的 最高 分数。

示例 1:

输入:questions = [ [3,2],[4,3],[4,4],[2,5] ]
输出:5
解释:解决问题 0 和 3 得到最高分。

  • 解决问题 0 :获得 3 分,但接下来 2 个问题都不能解决。
  • 不能解决问题 1 和 2
  • 解决问题 3 :获得 2 分
    总得分为:3 + 2 = 5 。没有别的办法获得 5 分或者多于 5 分。

示例 2:

输入:questions = [ [1,1],[2,2],[3,3],[4,4],[5,5] ]
输出:7
解释:解决问题 1 和 4 得到最高分。

  • 跳过问题 0
  • 解决问题 1 :获得 2 分,但接下来 2 个问题都不能解决。
  • 不能解决问题 2 和 3
  • 解决问题 4 :获得 5 分
    总得分为:2 + 5 = 7 。没有别的办法获得 7 分或者多于 7 分。

提示:

  • 1 <= questions.length <= 105
  • questions[i].length == 2
  • 1 <= pointsi, brainpoweri <= 105;

Solution

考虑到,这里我们所有的状态都应该根据已有的来更新,所以倒着DP就可以
正着应该也行,但是得用刷表法,这个下个Part介绍

class Solution {
public:
    long long mostPoints(vector<vector<int>>& questions) {
        int n=questions.size();
        vector<vector<long long>>ma(n+5,vector<long long>(2,0));
        for(int i=0;i<=n-1;i++){
            ma[n-i][0]=questions[i][0];
            ma[n-i][1]=questions[i][1];
        }
        vector<long long>dp(n+5,0);
        dp[1]=ma[1][0];
        for(int i=2;i<=n;i++){
            int j=i-ma[i][1]-1;
            if(j<=0) dp[i]=max(ma[i][0],dp[i-1]);
            else dp[i]=max(dp[i],max(dp[i-1],dp[j]+ma[i][0]));
        }
        return dp[n];
    }
};

1749:任意子数组和的绝对值的最大值

请你找出 nums 中 和的绝对值 最大的任意子数组(可能为空),并返回该 最大值 。

abs(x) 定义如下:

  • 如果 x 是负整数,那么 abs(x) = -x 。
  • 如果 x 是非负整数,那么 abs(x) = x 。

示例 1:

输入:nums = [1,-3,2,3,-4]
输出:5
解释:子数组 [2,3] 和的绝对值最大,为 abs(2+3) = abs(5) = 5 。

示例 2:

输入:nums = [2,-5,1,-4,3,-2]
输出:8
解释:子数组 [-5,1,-4] 和的绝对值最大,为 abs(-5+1-4) = abs(-8) = 8 。

提示:

  • 1 <= nums.length <= 105
  • -104 <= nums[i] <= 104

Solution

我们通过观察可以发现,对于这种绝对值的题目,它不是连续最大子段和就是连续最小子段和,跑两次就可以。

class Solution {
public:
    int maxAbsoluteSum(vector<int>& nums) {
        int n=nums.size();
        vector<int>dp(n+5,0);
        vector<int>fdp(n+5,0);
        int res=0,ret=0;
        dp[0]=nums[0];
        fdp[0]=nums[0];
        for(int i=1;i<=n-1;i++){
            dp[i]=max(dp[i-1]+nums[i],nums[i]);
            fdp[i]=min(fdp[i-1]+nums[i],nums[i]);
        }
        for(int i=0;i<=n-1;i++){
            res=max(res,abs(dp[i]));
            ret=max(ret,abs(fdp[i]));
        }
        return max(res,ret);
    }
};

918:环形子数组的最大和

环形数组 意味着数组的末端将会与开头相连呈环状。形式上, nums[i] 的下一个元素是 nums[(i + 1) % n] , nums[i] 的前一个元素是 nums[(i - 1 + n) % n] 。

子数组 最多只能包含固定缓冲区 nums 中的每个元素一次。形式上,对于子数组 nums[i], nums[i + 1], ..., nums[j] ,不存在 i <= k1, k2 <= j 其中 k1 % n == k2 % n 。

示例 1:

输入:nums = [1,-2,3,-2]
输出:3
解释:从子数组 [3] 得到最大和 3

示例 2:

输入:nums = [5,-3,5]
输出:10
解释:从子数组 [5,5] 得到最大和 5 + 5 = 10

示例 3:

输入:nums = [3,-2,2,-3]
输出:3
解释:从子数组 [3] 和 [3,-2,2] 都可以得到最大和 3

提示:

  • n == nums.length
  • 1 <= n <= 3 * 104
  • -3 * 104 <= nums[i] <= 3 * 104

Solution

也是没想出来,看来环形的题目有时候还挺坑
我们脑袋里要自动把环形的尝试转化成链状的,切开之后?
我们会发现,一种是不跨过切口的,也就是普通的子数组和
另一种是跨过切口的,这种怎么转化为已知的?
没错,正难则反,我们取反,求它的补集(不跨过切口)的最小值

class Solution {
public:
    int maxSubarraySumCircular(vector<int>& nums) {
        int n=nums.size();
        const int INF=1e9;
        int ans=0;
        vector<int>dp(n+5,0);
        vector<int>fdp(n+5,0);
        dp[0]=nums[0];
        ans=nums[0];
        fdp[0]=nums[0];
        for(int i=1;i<=n-1;i++){
            dp[i]=max(dp[i-1]+nums[i],nums[i]);
            ans+=nums[i];
            fdp[i]=min(fdp[i-1]+nums[i],nums[i]);
        }   
        int res=-INF;
        int ret=INF;
        for(int i=0;i<=n-1;i++) res=max(res,dp[i]);
        for(int i=0;i<=n-1;i++) ret=min(ret,fdp[i]);
        if(ret==ans) return res;
        return max(res,ans-ret);
    }
};

2321:拼接数组的最大分数

你可以选择两个整数 left 和 right ,其中 0 <= left <= right < n ,接着 交换 两个子数组 nums1[left...right] 和 nums2[left...right] 。

  • 例如,设 nums1 = [1,2,3,4,5] 和 nums2 = [11,12,13,14,15] ,整数选择 left = 1 和 right = 2,那么 nums1 会变为 [1,12,13,4,5] 而 nums2 会变为 [11,2,3,14,15] 。

你可以选择执行上述操作 一次 或不执行任何操作。

数组的 分数 取 sum(nums1) 和 sum(nums2) 中的最大值,其中 sum(arr) 是数组 arr 中所有元素之和。

返回 可能的最大分数 。

子数组 是数组中连续的一个元素序列。arr[left...right] 表示子数组包含 nums 中下标 left 和 right 之间的元素(含 下标 left 和 right 对应元素)。

示例 1:

输入:nums1 = [60,60,60], nums2 = [10,90,10]
输出:210
解释:选择 left = 1 和 right = 1 ,得到 nums1 = [60,90,60] 和 nums2 = [10,60,10] 。
分数为 max(sum(nums1), sum(nums2)) = max(210, 80) = 210 。

示例 2:

输入:nums1 = [20,40,20,70,30], nums2 = [50,20,50,40,20]
输出:220
解释:选择 left = 3 和 right = 4 ,得到 nums1 = [20,40,20,40,20] 和 nums2 = [50,20,50,70,30] 。
分数为 max(sum(nums1), sum(nums2)) = max(140, 220) = 220 。

示例 3:

输入:nums1 = [7,11,13], nums2 = [1,1,1]
输出:31
解释:选择不交换任何子数组。
分数为 max(sum(nums1), sum(nums2)) = max(31, 3) = 31 。

提示:

  • n == nums1.length == nums2.length
  • 1 <= n <= 105
  • 1 <= nums1[i], nums2[i] <

Solution

这道题虽然是hard,但是感觉还是比较好想
无非是求调换的分数差,维护一个最大最小值

class Solution {
public:
    int maximumsSplicedArray(vector<int>& nums1, vector<int>& nums2) {
        const int INF=1e9;
        int n=nums1.size();
        vector<int>ma(n+5,0);
        for(int i=0;i<=n-1;i++) ma[i]=nums1[i]-nums2[i];
        int ans_1=0,ans_2=0;
        for(int i=0;i<=n-1;i++){
            ans_1+=nums1[i];
            ans_2+=nums2[i];
        }
        vector<int>dp(n+5,0);
        vector<int>fdp(n+5,0);
        dp[0]=ma[0];fdp[0]=ma[0];
        for(int i=1;i<=n-1;i++){
            dp[i]=max(dp[i-1]+ma[i],ma[i]);
            fdp[i]=min(fdp[i-1]+ma[i],ma[i]);
        }
        int res=-INF;
        int ret=INF;
        for(int i=0;i<=n-1;i++){
            res=max(res,dp[i]);
            ret=min(ret,fdp[i]);
        }
        int re=max(max(ans_1-res,ans_2+res),max(ans_1-ret,ans_2+ret));
        return re;
    }
};

152:乘积最大子数组

测试用例的答案是一个 32-位 整数。

示例 1:

输入: nums = [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。

示例 2:

输入: nums = [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。

提示:

  • 1 <= nums.length <= 2 * 104
  • -10 <= nums[i] <= 10
  • nums 的任何子数组的乘积都 保证 是一个 32-位 整数

Solution

这道题也没想出来,其实不难,就是一个思想
我们要维护的东西可能不止一个
如果同时维护最大值和最小值,答案就呼之欲出了

class Solution {
public:
    int maxProduct(vector<int>& nums) {
       int n=nums.size();
        const int INF=(1<<31)-1;
        vector<int>dp(n+5,INF);
        vector<int>fdp(n+5,-INF);
        dp[0]=nums[0];fdp[0]=nums[0];
        for(int i=1;i<=n-1;i++){
            if(nums[i]<0){
                dp[i]=max(fdp[i-1]*nums[i],nums[i]);
                fdp[i]=min(dp[i-1]*nums[i],nums[i]);
            } 
            else if(nums[i]==0){
                dp[i]=0;fdp[i]=0;
            }
            else if(nums[i]>0){
                dp[i]=max(dp[i-1]*nums[i],nums[i]);
                fdp[i]=min(fdp[i-1]*nums[i],nums[i]);
            }
        }
        int res=-INF;
        for(int i=0;i<=n-1;i++) res=max(res,dp[i]);
        return res;
    }
};
posted @ 2025-07-01 23:20  elainafan  阅读(17)  评论(0)    收藏  举报