一步一个台阶,两个台阶,三个台阶,…,直到 m个台阶。问有多少种不同的方法可以爬到楼顶呢?
1阶,2阶,… m阶就是物品,楼顶就是背包。每一阶可以重复使用,例如跳了1阶,还可以继续跳1阶。问跳到楼顶有几种方法其实就是问装满背包有几种方法。此时大家应该发现这就是一个完全背包问题了!
确定dp数组以及下标的含义:dp[i]:爬到有i个台阶的楼顶,有dp[i]种方法。
确定递推公式:那么递推公式为:dp[i] += dp[i - j]
dp数组如何初始化:既然递归公式是 dp[i] += dp[i - j],那么dp[0] 一定为1,dp[0]是递归中一切数值的基础所在,如果dp[0]是0的话,其他数值都是0了。
确定遍历顺序:这是背包里求排列问题,先背包,后物品。
Int clam(int m,int n){//n表示楼层高度,m表示步数选择1到m
Vector<int> dp(n+1,0);
Dp[0]=1;
For(int i=0;i<=n;i++){
For(int j=1;i<=m;j++){
If(i-j>=0){
Dp[i]+=dp[i-j];
}
}
}
Return dp[n];
}
时间复杂度: O(nm);空间复杂度: O(n)
给你一个整数数组 coins
,表示不同面额的硬币;以及一个整数 amount
,表示总金额。计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1
。你可以认为每种硬币的数量是无限的。
题目中说每种硬币的数量是无限的,可以看出是典型的完全背包问题。
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
vector<int> dp(amount+1,INT_MAX);
dp[0]=0;
for(int i=0;i<=amount;i++){
for(int j=0;j<coins.size();j++){
if(i-coins[j]>=0 && dp[i-coins[j]]!=INT_MAX){
dp[i] = min(dp[i],dp[i-coins[j]]+1);
}
}
}
return dp[amount]==INT_MAX?-1:dp[amount];
}
};
时间复杂度: O(n * amount),其中 n 为 coins 的长度;空间复杂度: O(amount)
给你一个整数 n
,返回 和为 n
的完全平方数的最少数量 。完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1
、4
、9
和 16
都是完全平方数,而 3
和 11
不是。
确定dp数组(dp table)以及下标的含义:dp[j]:和为j的完全平方数的最少数量为dp[j]
确定递推公式:dp[j] 可以由dp[j - i * i]推出, dp[j - i * i] + 1 便可以凑成dp[j]。此时我们要选择最小的dp[j],所以递推公式:dp[j] = min(dp[j - i * i] + 1, dp[j]);
dp数组如何初始化:dp[0]表示 和为0的完全平方数的最小数量,那么dp[0]一定是0。从递归公式dp[j] = min(dp[j - i * i] + 1, dp[j]);中可以看出每次dp[j]都要选最小的,所以非0下标的dp[j]一定要初始为最大值,这样dp[j]在递推的时候才不会被初始值覆盖。
class Solution {
public:
int numSquares(int n) {
vector<int> dp(n+1,INT_MAX);
dp[0]=0;
for(int i=0;i<=n;i++){
for(int j=1;j*j<=i;j++){
dp[i] = min(dp[i],dp[i-j*j]+1);
}
}
return dp[n];
}
};
给你一个字符串 s
和一个字符串列表 wordDict
作为字典。请你判断是否可以利用字典中出现的单词拼接出 s
。**注意:**不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
单词就是物品,字符串s就是背包,单词能否组成字符串s,就是问物品能不能把背包装满。拆分时可以重复使用字典中的单词,说明就是一个完全背包!
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
unordered_set<string> wordset(wordDict.begin(),wordDict.end());
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 word=s.substr(j,i-j);//substr(起始位置,截取的个数)
if(wordset.find(word)!=wordset.end() && dp[j]){
dp[i]=true;
}
}
}
return dp[s.size()];
}
};
时间复杂度:O(n^3),因为substr返回子串的副本是O(n)的复杂度(这里的n是substring的长度);空间复杂度:O(n)
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
当前房屋偷与不偷取决于 前一个房屋和前两个房屋是否被偷了。所以这里就更感觉到,当前状态和前面状态会有一种依赖关系,那么这种依赖关系都是动规的递推公式。当然以上是大概思路,打家劫舍是dp解决的经典问题,接下来我们来动规五部曲分析如下:
class Solution {
public:
int rob(vector<int>& nums) {
if(nums.size()==0){
return 0;
}
if(nums.size()==1){
return nums[0];
}
vector<int> dp(nums.size());
dp[0]=nums[0];
dp[1]=max(nums[0],nums[1]);
for(int i=2;i<nums.size();i++){
dp[i]=max(dp[i-2]+nums[i],dp[i-1]);
}
return dp[nums.size()-1];
}
};
时间复杂度: O(n);空间复杂度: O(n)
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。
class Solution {
public:
int robpart(vector<int> &numspart,int start,int end){
if(end==start) return numspart[start];
vector<int> dp(numspart.size());
dp[start] = numspart[start];
dp[start+1] = max(numspart[start],numspart[start+1]);
for(int i=start+2;i<=end;i++){
dp[i] = max(dp[i-2]+numspart[i],dp[i-1]);
// cout<
}
return dp[end];
}
int rob(vector<int>& nums) {
if(nums.size()==0){
return 0;
}
if(nums.size()==1){
return nums[0];
}
int left = robpart(nums,0,nums.size()-2);
// cout<
int right = robpart(nums,1,nums.size()-1);
// cout<
// return left>right?left:right;
return max(left,right);
}
};
小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root
。除了 root
之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。给定二叉树的 root
。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额 。
这道题目算是树形dp的入门题目,因为是在树上进行状态转移,在讲解二叉树的时候说过递归三部曲,那么下面我以递归三部曲为框架,其中融合动规五部曲的内容来进行讲解。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<int> robtree(TreeNode *cur){
if(cur==nullptr){
return vector<int>{0,0};
}
vector<int> left=robtree(cur->left);
vector<int> right=robtree(cur->right);
int val1=cur->val+left[0]+right[0];
int val2=max(left[0],left[1])+max(right[0],right[1]);
return {val2,val1};
}
int rob(TreeNode* root) {
vector<int> res=robtree(root);
return max(res[0],res[1]);
}
};
时间复杂度:O(n),每个节点只遍历了一次;空间复杂度:O(log n),算上递推系统栈的空间