文章目录
- 引言
- 新作
- 爬楼梯
- 个人实现
- 参考实现
- 杨辉三角
- 个人实现
- 参考实现
- 打家劫舍
- 个人实现
- 参考实现
- 完全平方数
- 个人实现
- 参考实现
- 总结
引言
- 回家以来,和朋友的聚会暂时告一段落了,后面就准备闭关,继续准备秋招了,不能在浪费时间了。
- 加油,虽然我的实习效果不怎么样,但是秋招加油好好准备总是有效果的。
新作
爬楼梯
-
题目链接
重点 -
有两种爬楼梯得方式,分别是爬一步和爬两步两种策略,动态规划可以实现
-
n最大是45
个人实现
- 状态计算方程应该是f[i] = f[i - 1] + f[i - 2];
- 这道题是很基础的动态规划题目,总体实现起来比较简单。
class Solution {
public:
int f[50];
int climbStairs(int n) {
f[0] = 1;
for (int i = 1; i <= n; ++i) {
f[i] += f[i - 1] ;
if (i >= 2) f[i] += f[i -2];
}
return f[n];
}
};
参考实现
- 声明vector的时候可以指定对应的vector的长度,然后通过该种方式可以进一步节省内存。
class Solution {
public:
int climbStairs(int n) {
vector<int>f(n + 1);
f[0] = 1;
f[1] = 1;
for (int i = 2; i <= n; i ++ )
f[i] = f[i - 1] + f[i - 2];
return f[n];
}
};
杨辉三角
- 题目链接
注意点
- 生成杨辉三角的前n行
- 杨辉三角是当前数字等于肩膀上两个数字之和,所以如何获取肩膀上的两个数的具体的值十分重要。
- 同时判定每一行结束的位置也十分重要。
- 开头和末尾的数字一定是的1
个人实现
- 使用动态规划实现,但是如何定位具体的数字的坐标?
- f[i][j] = f[i - 1][j - 1] + f[i - 1][j]
问题
- 在实现过程中,遇到一个问题,这个映射关系还是蛮混乱的,如何写才能更加简单清晰明了,不用像之前一样,每一次都要减去对应的值。
注意
- 这里第二重小循环遍历的时候,终止条件是i,并不是n,这里写错了好几次,所以会出问题。
class Solution {
public:
vector<vector<int>> generate(int n) {
vector<vector<int>> res = {{1},{1,1}};
if(n == 1) return {{1}};
if(n == 2) return res;
for(int i = 3;i <= n;i ++){
// 这样写是否可以成功?会不会自动加上一个数据?
res.push_back({});
res[i - 1].push_back(1);
for(int j = 2;j < i; j++ ){
// cout<<j<<",:"<<res[i -2].size()<<endl;
// cout<<res[i -2][j - 1]<<endl;
res[i - 1].push_back(res[i - 2][j - 2] + res[i -2][j - 1]);
}
res[i - 1].push_back(1);
}
return res;
}
};
参考实现
参考分析
- 下述是y总的代码,对于每一次都需要进行变换的二次数组,可以使用一个中间变量进行保存,这样写就方便很多。
- 直接获取数组的最后一个vector,然后再新建一个vector,然后改变新建的vector,生成的新的vector数组。
class Solution {
public:
vector<vector<int>> generate(int numRows) {
vector<vector<int>> res;
if (!numRows) return res;
res.push_back(vector<int>(1, 1));
for (int i = 1; i < numRows; i ++ )
{
vector<int> &last = res.back();
vector<int> temp;
temp.push_back(1);
for (int i = 0; i + 1 < last.size(); i ++ )
temp.push_back(last[i] + last[i + 1]);
temp.push_back(1);
res.push_back(temp);
}
return res;
}
};
作者:yxc
链接:https://www.acwing.com/solution/content/209/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处
打家劫舍
题目链接
注意
- 如果同时闯入相邻房屋就会触发报警==》不能同时选择相邻的两个元素
- 求取最高的金额
个人实现
-
这道题就是一个标准的状态DP,而且和之前的大盗阿福很像,之前的分析链接如下
-
股票买卖状态机
-
股票买卖状态机2
-
这里获取对应的状态即可,每一个房屋只有两个状态,取或者不取,然后如果上一个取了,这一次就不能取
-
f[i] = max(f[i - 1],f[i - 1] + w[i])
- 感觉有点小问题,这个状态转移方程有点问题,上一个取了,我怎么知道上一个取了?我需要管吗?很明显是需要的,因为上一个状态是会影响当前的状态,但是怎么表示?
- 两个矩阵吗?明显不是的,想想之前的股票买卖问题,我可以定义两个状态,相当于是两个状态转移方程
- f[i][0]表示第i个房屋已经抢了
- f[i][1]表示第i个房屋没有被抢
-
那么这样定义之后,这种状态方程就简单很多了。具体状态转移方程如下
- f[i][0] = max(f[i-1][1] + w[i])
- 当前房屋被抢了,只有一种可能会变成当前状态,就是上一个房屋没有被抢
- f[i][1] = max(f[i-1][1] ,f[i-1][0] ),总共有两种情况
- 上一个房屋被抢了
- 上一个房屋没有被抢
- f[i][0] = max(f[i-1][1] + w[i])
具体实现如下
class Solution {
public:
int rob(vector<int>& n) {
vector<vector<int>> f;
// 更新一下f[0][0]
f.push_back({n[0],0});
int l = n.size();
for(int i = 1;i < l;i ++){
f.push_back({0,0});
cout<<f[i][0]<<" "<<f[i][1]<<endl;
// 第i个家,抢了
f[i][0] = f[i-1][1] + n[i];
// 第i个家,不抢
f[i][1] = max(f[i-1][0],f[i-1][1]);
}
return max(f[l-1][1],f[l-1][0]);
}
};
参考实现
- 我写的还是太复杂冗余了,看看这个代码,写的多简洁。
- 既然同时申请二维的vector是一件困难的事情,为什么不直接申请两个一维的数组,这样效果不是更好吗?
- 而且刚才还在对数组的索引产生疑惑,不知道怎么处理更好,为什么不直接从第二天开始索引?
int rob(vector<int> nums)
int n = nums.size();
vector<int> f(n + 1),g(n + 1);
for(int i = 1;i <= n;i ++){
f[i] = g[ i -1] + nums[i -1];
g[i] = max(f[i - 1],g[i - 1]);
}
return max(f[n],g[n]);
}
完全平方数
- 题目链接
重点 - 完全平方数:自己和自己的乘积的和
- 和为n 的完全平方数的最小数量
个人实现
- 这道题应该不是上一题一样的状态DP,回顾一下我们经历过那几个类型的DP
- 背包问题
- 数字三角问题
- 树形DP
- 单调队列
- 最大上升子序列
- 状态压缩DP
- 状态DP
- 区间DP
- 从目前来看,感觉很像是一个完全背包问题,然后计算的是最小值,状态转移矩阵如下
- f[i][j]
- i表示第几个完全平方数
- j表示当前目标值
- f[i][j] = f[i][k] k为第i个物体不断重复k次,知道超过背包容量
- f[i][j]
class Solution {
public:
int numSquares(int n) {
// 创建并保存每一个需要遍历的变量
vector<int> exp;
for(int i = 1;i*i <= n;i ++) {
exp.push_back(i*i);
if(i * i == n) return 1;
}
// 创建需要保存数据的二维矩阵
int l = exp.size();
// int f[l + 1][n + 1];
// memset(f,INT_MAX,sizeof(f));
vector<vector<int>> f(l + 1);
for(int s = 0; s <= n;s ++)
// f[0][s] = s;
f[0].push_back(s);
for(int i = 1;i < l;i ++){
for(int s = 0; s <= n;s ++){
f[i].push_back(INT_MAX);
// 当前的数字不放任何物体
f[i][s] = f[i - 1][s];
for(int j = 1 ;j * exp[i] <= s; j ++ ){
// 靠,这里看错了,调试了半天,居然是结果弄错了!
// cout<<"exp[i]:"<<exp[i]<<" i:"<<i<<" s:"<<s<<" j"<<j<<endl;
// cout<<f[i - 1][s - j * exp[i]]<<endl;
// cout<<f[i][s]<<endl;
f[i][s] = min(f[i][s] , f[i - 1][s - j * exp[i]] + j);
}
}
}
return f[l - 1][n];
}
};
- 很明显,这里要使用公式转换和滚动矩阵进行优化,这里的代码我会推导,但是没有时间了,直接看参考代码吧
参考实现
- 下面这个代码太不寻常了,肯定是使用了我不知道是什么数学推理,或者没有发现的规律,已经不是动态规划l了。
- 当真的是的!
class Solution {
public:
bool check(int x) {
int r = sqrt(x);
return r * r == x;
}
int numSquares(int n) {
if (check(n)) return 1;
for (int a = 1; a <= n / a; a ++ )
if (check(n - a * a))
return 2;
while (n % 4 == 0) n /= 4;
if (n % 8 != 7) return 3;
return 4;
}
};
动态规划问题二
有一个关键的集合表示方法,就是当前的集合可以使用一个平方数进行表示,然后剩下的数字能不能使用一个平方和数字使用保存。
class Solution {
public:
int numSquares(int n) {
vector<int> f(n + 1);
for (int i = 1; i <= n; i++) {
int minn = INT_MAX;
for (int j = 1; j * j <= i; j++) {
minn = min(minn, f[i - j * j]);
}
f[i] = minn + 1;
}
return f[n];
}
};
作者:力扣官方题解
链接:https://leetcode.cn/problems/perfect-squares/solutions/822940/wan-quan-ping-fang-shu-by-leetcode-solut-t99c/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
总结
- 今天状态还行,上午做的两道简单题基本上都是在限定时间内做出来的。
- 虽然之前浪费了很多时间,但是这些浪费也不是完全无用的,至少到现在,做的三道动态规划题目,都能够在规定的二十五分钟内完成,还是有用的。
- 今天晚上两道中等题,都是做出来了,第一道题AC了,第二道题大部分样例都是AC的,但是超时了。 后续可以在修改。
- 今天少做了一题,明天继续加油!!