Leetcode算法入门第十二天(动态规划)
70. 爬楼梯
题目描述
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
样例
输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
思路一
这道题一看就是经典的递归题型,我们很容易知道要到达第n阶台阶,最后一步可能跨出一阶,也可能跨出两阶,所以我们有递推式第n阶f(n)=f(n-1)+f(n-2),但是会发现单纯第递归肯定会造成很多不必要的重复计算,所以我们用数组进行存储。
参考代码
class Solution {
public:
int climb(vector<int>&dp,int n)
{
//之前计算的,直接返回:
if(dp[n]>0)
{
return dp[n];
}
//n=1和n=2是特殊情况必须单独考虑
if(n==1)
{
dp[n]= 1;
}
else if(n==2)
{
dp[n]= 2;
}
else//一般的情况
{
dp[n]=climb(dp,n-1)+climb(dp,n-2);
}
//返回修改后的dp[n]
return dp[n];
}
int climbStairs(int n) {
vector<int> dp(n+1);
int res=climb(dp,n);
return res;
}
};
思路二
动态规划
:我们可以发现只要得到前两阶的方案数,相加就得到当前阶的方案数,所以我们可以得到动态转移方程dp[i]=dp[i-1
+dp[i-2],直接通过循环,不断计算,直到得到结果返回即可。
参考代码
class Solution {
public:
//动态规划:直接用循环计算需要的结果,不用递归了
int climbStairs(int n) {
if(n==1) return 1;
vector<int> dp(n+1);
//前两阶直接写出来
dp[1]=1;
dp[2]=2;
//后面一直到n阶利用前面的两阶不断计算
for(int i=3;i<=n;i++)
{
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
};
思路二优化
我们可以发现其实思路二的状态数组我们每次都只需要用到前两个,所以造成不必要的空间浪费,使用两个变量代替即可,这里其实就是
滚动数组
的思想,不断向前滚动,直到计算出结果。
参考代码
class Solution {
public:
//动态规划:直接用循环计算需要的结果,不用递归了
//用滚动数组进行优化:其实只需要使用两个变量即可。
int climbStairs(int n) {
if(n==1) return 1;
//前两阶直接写出来
int first=1;
int second=2;
int temp;
//后面一直到n阶利用前面的两阶不断计算
for(int i=3;i<=n;i++)
{
//临时存储前两个位置之和
temp=first+second;
//更新前两个结果
first=second;
//当前结果
second=temp;
}
return second;
}
};
198. 打家劫舍
题目描述
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
样例
输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
思路
动态规划
:如果只有一个房屋,那么金额最大值就是这个。如果有两个房屋,因为相邻的不可同时偷,那么金额最大值就是这两个房屋的较大值。那么对于第i个房屋,要么被偷,要么不被偷:
1.被偷
:则第i-1房屋不能偷,偷窃总金额=前i-2个房屋的最大金额加上当前房屋的金额
2.不被偷
:则总金额=前i-1个房屋的最大金额
则最大值为这两种情况中取较大值,所以我们有
动态转移方程
:dp[i]=max(dp[i-2]+nums[i],dp[i-1]);
(其中dp[i]表示前i个房屋偷窃的最大金额)
参考代码
class Solution {
public:
int rob(vector<int>& nums) {
if(nums.size()==1) return nums[0];
//动态规划
vector<int> dp(nums.size()+1);
//只有一个房屋
dp[0]=nums[0];
//有两个房屋
dp[1]=max(nums[0],nums[1]);
//计算后面i个房屋
for(int i=2;i<nums.size();i++)
{
dp[i]=max(dp[i-2]+nums[i],dp[i-1]);
}
return dp[nums.size()-1];
}
};
思路优化
同上一道题一样,可以用滚动数组来优化,具体如下
参考代码
class Solution {
public:
int rob(vector<int>& nums) {
if(nums.size()==1) return nums[0];
//动态规划:滚动数组优化
//只需要存储前两间屋子的最高金额
int first=nums[0];
int second=max(nums[0],nums[1]);
for(int i=2;i<nums.size();i++)
{
int temp=max(first+nums[i],second);
first=second;
second=temp;
}
return second;
}
};
120. 三角形最小路径和
题目描述
给定一个三角形 triangle ,找出自顶向下的最小路径和。
每一步只能移动到下一行中相邻的结点上。相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。也就是说,如果正位于当前行的下标 i ,那么下一步可以移动到下一行的下标 i 或 i + 1 。
样例
输入:triangle = [[2],[3,4],[6,5,7],[4,1,8,3]]
输出:11
解释:如下面简图所示:
2
3 4
6 5 7
4 1 8 3
自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。
思路
动态规划法
:要找到自顶向下的最小路径和,如果只有一层,那么最小路径和就是第一层的那个值。如果有i层,我们可以计算从顶端到第i层的每个顶点的最小路径,这一层中的最小值就是我们的答案。那么怎么计算呢?如果计算到位置【i,j】处的最小路径,那么可以通过上面一层相同位置(i-1,j)和左边位置(i-1,j)(
因为只能向下和右下一格走
)加上当前节点的路径即可,所以我们有动态转移方程:
dp[i][j]=min(dp[i-1][j-1],dp[i-1][j])+triangle[i][j];
,注意一些边界,比如纵坐标不能达到-1,也不能超过上一层的长度i-1.
参考代码
class Solution {
public:
int minimumTotal(vector<vector<int>>& triangle) {
int n=triangle.size();
int m=triangle[n-1].size();
//dp数组dp[i][j]表示自顶端到当前位置【i,j】的最小路径和
vector<vector<int>> dp(n,vector<int>(m));
dp[0][0]=triangle[0][0];
for(int i=1;i<n;i++)
{
//已经最靠左边了,纵坐标没有到-1
dp[i][0]=dp[i-1][0]+triangle[i][0];
//已经最靠右边,上一层纵坐标没有到i
dp[i][i]=dp[i-1][i-1]+triangle[i][i];
for(int j=1;j<i;j++)
{
//当前最小路径和就是上一层的相同下标或者左边下标
dp[i][j]=min(dp[i-1][j-1],dp[i-1][j])+triangle[i][j];
}
}
//最后一层里的最小值就是我们要的结果
int res=dp[n-1][0];
for(int j=0;j<m;j++)
{
if(res>dp[n-1][j])
{
res=dp[n-1][j];
}
}
return res;
}
};