动态规划题目类型:
动规五部曲:
(1) dp数组以及下表的含义
(2)递推公式
(3)dp数组如何初始化
(4)遍历顺序:背包类尤其重要,两层for循环,先遍历背包再遍历物体
(5)打印dp数组:看看dp数组是否正确
分析之后发现就是斐波那契数的问题。这道题难点在于递推公式
拓展:如果一步可以走m个台阶,如何做
就是一步一个台阶,两个台阶,三个台阶,直到 m个台阶,有多少种方法爬到n阶楼顶。
一个绝佳的大厂面试题,第一道题就是单纯的爬楼梯,然后看候选人的代码实现,如果把dp[0]的定义成1了,就可以发难了,为什么dp[0]一定要初始化为1,此时可能候选人就要强行给dp[0]应该是1找各种理由。那这就是一个考察点了,对dp[i]的定义理解的不深入。
然后可以继续发难,如果一步一个台阶,两个台阶,三个台阶,直到 m个台阶,有多少种方法爬到n阶楼顶。这道题目leetcode上并没有原题,绝对是考察候选人算法能力的绝佳好题。
这一连套问下来,候选人算法能力如何,面试官心里就有数了。
其实大厂面试最喜欢的问题就是这种简单题,然后慢慢变化,在小细节上考察候选人。
class Solution {
public:
int climbStairs(int n) {
vector<int> dp(n + 1, 0);
dp[0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) { // 把m换成2,就可以AC爬楼梯这道题
if (i - j >= 0) dp[i] += dp[i - j];
}
}
return dp[n];
}
};
代码中m表示最多可以爬m个台阶。
分析可知,到达每一步,要么是从上方到达,要么是从左方到达,也就是说[i][j]的来源是[i-1][j]和[i][j-1],dp[i][j]的含义是到达[i][j]的路径条数,dp[i][j]=dp[i-1][j]+dp[i][j-1]【边界独立处理(左边和上边)】
dp[i]:对i拆分得到的最大的乘积。如果拆分v成2个数,j和i-j,那么继续拆分,就是j*dp[i-j]个数
掌握01背包和完全背包
给出一个总数,一些物品,问能否凑成这个总数。这是典型的背包问题!
一种物品只有取/不取两种状态
dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少
题目链接
递推公式dp[i][j]=max(dp[i-1,j],dp[i-1,j-weight[i]]+value[i])
c++万能头文件(在ACM模式中使用)#include
更好的方法是用滚动一维数组,一维dp遍历的时候,背包是从大到小,就是遍历顺序是j=bag,j–。并且遍历顺序只能是先遍历物品嵌套遍历背包容量
01背包中的for循环,倒序遍历是为了让物品使用一次,那么改成正序遍历就可以使用无数次
完全背包二维DP数组的递推公式为:dp[i][j] = max(dp[i - 1][j], dp[i][j - weight[i]] + value[i])
一维递推:dp[j]=dp[j]+dp[j-coins[i]]
求组合与求排列的遍历顺序是不同的
如果求组合数就是外层for循环遍历物品,内层for遍历背包。
如果求排列数就是外层for遍历背包,内层for循环遍历物品。
总结:
求组合数:动态规划:518.零钱兑换II (opens new window)求排列数:动态规划:377. 组合总和 Ⅳ (opens new window)、动态规划:70. 爬楼梯进阶版(完全背包) (opens new window)求最小数:动态规划:322. 零钱兑换 (opens new window)、动态规划:279.完全平方数
华为
能否分成两个和相等的集合,就是看是否存在和为sum/2的子数组
本题中每一个元素的数值既是重量,也是价值
字节笔试
就是看能否尽量将石头分成两堆重量尽可能相同的子堆,然后和416分割等和子集就;类似了
注意组合和排列方式的代码区别
组合数:
for (int i = 0; i < coins.size(); i++) { // 遍历物品
for (int j = coins[i]; j <= amount; j++) { // 遍历背包容量
dp[j] += dp[j - coins[i]];
}
}
排列数:
for (int j = 0; j <= amount; j++) { // 遍历背包容量
for (int i = 0; i < coins.size(); i++) { // 遍历物品
if (j - coins[i] >= 0) dp[j] += dp[j - coins[i]];
}
}
排列类型
link
百度、wxg、pdd
动规五步:
① dp数组含义:凑成总金额所需的 最少的硬币个数
② 递推公式关系:dp[j] = min(dp[j - coins[i]] + 1, dp[j]);解释:每遍历一个coins的元素,dp[j]都会更新,更新之后是dp[j]=(dp[j-coins[i])+1,需要和之前的对比找出更小的
③ 初始化:因为是找最小的。所以初始化为一个最大的数INT_MAX
④ 遍历顺序:先物品后背包
⑤ dp数组举例推导
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
vector<int> dp(amount+1,INT_MAX);//注意是amount+1,不然会报错
dp[0]=0;
for(int i=0;i<coins.size();i++){
for(int j=coins[i];j<=amount;j++){
if(dp[j-coins[i]]!=INT_MAX){//如果是初始值则跳过,因为如果是初始值,证明无法凑成当前的amount
dp[j]=min(dp[j-coins[i]]+1,dp[j]);
}
}
}
if(dp[amount]==INT_MAX) return -1;
return dp[amount];
}
};
huawei,百度,美团
总数就是和–背包;每个完全平方数就是物品
完全平方数就是物品(可以无限件使用),凑个正整数n就是背包,问凑满这个背包最少有多少物品
h和零钱兑换是一样的
class Solution {
public:
int numSquares(int n) {
vector<int> dp(n+1,INT_MAX);
dp[0]=0;
for(int i=1;i*i<n;i++){//注意起始i从1开始
for(int j=i*i;j<=n;j++){//注意起始j从i*i开始
dp[j]=min(dp[j-i*i]+1,dp[j]);
}
}
return dp[n];
}
};
hauwei/字节
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
vector<bool> dp(s.size()+1,false);
dp[0]=true;
for(int i = 1;i<=s.size();i++){//先背包,注意需要<=而不是<
for(int j=0;j<i;j++){
string substr = s.substr(j,i-j);
if(dp[j]&&find(wordDict.begin(),wordDict.end(), substr)!=wordDict.end()){
dp[i] = true;
}
}
}
return dp[s.size()];
}
};