【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;
}
};

浙公网安备 33010602011771号