动态规划刷题总结(二)

动态规划刷题总结(二)

求解动态规划问题的关键,通常在怎么找出最优子结构的递推公式,和dp数组中应该储存的什么信息;

解决了这两个问题,动态规划问题就很好求解了。但是,从最近的刷题经历来看,这两个问题是不好解

决的,不同的问题存在不同的变化,得进行不同方面的思考。

494.目标和

题目介绍:

给定一个非负整数数组,a1, a2, …, an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意

一个整数,你都可以从 + 或 -中选择一个符号添加在前面。

返回可以使最终数组和为目标数 S所有添加符号的方法数。

解题关键:

设全添加和为sum,全添负号和为-sum

建立一个size*(2*sum+1)大小的数组vector<vector<int> > dp(size,vector<int>(2*sum+1,0));

用来储存第i个数添加正号或者负号时和为-sumsum间某个值的方法数。

状态转移方程如下:

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

可换成如下递推方程

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

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

此时只需满足dp[i - 1][j]大于0即可。

代码如下:

class Solution {

public:

int findTargetSumWays(vector<int>& nums, int S) {

if(!nums.size())

return 0;

int size=nums.size();

int sum=0;

for(int i=0;i<size;i++)

sum+=nums[i];

vector<vector<int> > dp(size,vector<int>(2*sum+1,0));

dp[0][nums[0]+sum]=1;

dp[0][-nums[0]+sum]+=1;

for(int i=1;i<size;i++){

for(int j=-sum;j<=sum;j++){

if(dp[i-1][j+sum]>0){

dp[i][j+nums[i]+sum]+=dp[i-1][j+sum];

dp[i][j-nums[i]+sum]+=dp[i-1][j+sum];

}

}

}

return S>sum?0:dp[size-1][S+sum];

}

};

注意:因为数组的下标不能为负,所以所有的j都进行了**+sum的偏移**。

1049.最后一块石头的重量II

题目介绍:

有一堆石头,每块石头的重量都是正整数

每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那

么粉碎的可能结果如下:

如果 x == y,那么两块石头都会被完全粉碎

如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x

最后,最多只会剩下一块石头。返回此石头最小的可能重量。如果没有石头剩下,就返回 0。

解题关键:

将石头分成最相近的两堆,此时就能得出最小的最后一块石头的重量。

递推关系式dp[j]=max(dp[j],dp[j-stones[i]]+stones[i]);

其中int maxCap=sum/2;

dp[j]储存的是容量为j时的最大重量

代码如下:

class Solution {

public:

int lastStoneWeightII(vector<int>& stones) {

int size=stones.size();

if(stones.size()==1)

return stones[0];

if(size==0)

return 0;

int sum=0;

for(int i=0;i<size;i++){

sum+=stones[i];

}

int maxCap=sum/2;

vector<int> dp(sum/2+1);

for(int i=0;i<size;i++){

for(int j=maxCap;j>=stones[i];j--){

dp[j]=max(dp[j],dp[j-stones[i]]+stones[i]);

}

}

return sum-dp[maxCap]-dp[maxCap];

}

};

注意:j从后往前是因为进行了状态压缩,防止覆盖return的是两堆石头重量之差

935.骑士拨号器

题目介绍:

国际象棋中的骑士可以按下图所示进行移动:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9ErjWuGj-1605354371976)(C:\Users\xxj99\AppData\Roaming\Typora\typora-user-images\image-20201114193739901.png)]

这一次,我们将 “骑士” 放在电话拨号盘的任意数字键(如上图所示)上,接下来,骑士将会跳 N-1 步。

每一步必须是从一个数字键跳到另一个数字键。

每当它落在一个键上(包括骑士的初始位置),都会拨出键所对应的数字,总共按下 N 位数字。

你能用这种方式拨出多少个不同的号码

因为答案可能很大,所以输出答案模 10^9 + 7

解题关键:

此题其实比较简单,只需要注意当前数字由哪些数字跳过来就ok了

直接上代码:

class Solution {

public:

int knightDialer(int n) {

vector<vector<long long> > dp(n,vector<long long>(10,0));

int mod=(int)(pow(10,9)+7);

for(int i=0;i<10;i++)

dp[0][i]=1;

for(int i=1;i<n;i++){

dp[i][1]=(dp[i-1][6]+dp[i-1][8])%mod;

dp[i][2]=(dp[i-1][7]+dp[i-1][9])%mod;

dp[i][3]=(dp[i-1][4]+dp[i-1][8])%mod;

dp[i][4]=(dp[i-1][3]+dp[i-1][9]+dp[i-1][0])%mod;

dp[i][5]=0;

dp[i][6]=(dp[i-1][1]+dp[i-1][7]+dp[i-1][0])%mod;

dp[i][7]=(dp[i-1][2]+dp[i-1][6])%mod;

dp[i][8]=(dp[i-1][1]+dp[i-1][3])%mod;

dp[i][9]=(dp[i-1][2]+dp[i-1][4])%mod;

dp[i][0]=(dp[i-1][4]+dp[i-1][6])%mod;

}

int ans=0;

for(int i=0;i<10;i++)

ans=(ans+dp[n-1][i])%mod;

return ans;

}

};

注意:答案为什么要1e9+7呢?首先因为1e9+7是一个质数,模它减少了余数间存在公因数的可能;

dp[i][0]=(dp[i-1][4]+dp[i-1][6])%mod;

}

int ans=0;

for(int i=0;i<10;i++)

ans=(ans+dp[n-1][i])%mod;

return ans;

}

};

注意:答案为什么要1e9+7呢?首先因为1e9+7是一个质数,模它减少了余数间存在公因数的可能;

其次两个1e9+7相加小于2^31未超过int的范围,两个1e9+7相乘未超过long的范围

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值