70. 爬楼梯
假设你正在爬楼梯。需要 n
阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
思路:
- 一道经典的动态规划题目,由于每次可以爬一个或者两个台阶,因此可以将问题转化为最后一步是爬一阶还是爬两阶的问题。
- 状态转移方程为:
f(n)=f(n-1)+f(n-2)
- 如果利用递归来求解的话会产生过多的重复计算,时间空间复杂度都很高,所以要从小到大地进行迭代求解,计算每一步的临时变量。
- 我最初的算法是将每个中间变量都用数组存起来,然后挨个计算到
f(n)
,但是这样的空间复杂度太高了,不过能自己做出来还是不错啦~
class Solution {
public:
int climbStairs(int n) {
//动态规划
//状态方程f(n)=f(n-1)+f(n-2)
//先计算保存f(1)~f(n-1)的值到一个数组
vector<int>nums(n+1,0);
nums[1]=1;
if(n>1)
nums[2]=2;
//迭代赋值
for(int i=3;i<=n;i++)
{
nums[i]=nums[i-1]+nums[i-2];
}
return nums[n];
}
};
- 官方题解给的方法就很好,利用滚动数组,只用三个变量来存储每一步的结果,很好,又学到一招
class Solution {
public:
int climbStairs(int n) {
//动态规划
//状态方程f(n)=f(n-1)+f(n-2)
//p为f(n-2),q为f(n-1),r为f(n)
//f(0)=1,f(1)=1
int p=0,q=0,r=1;
for(int i=0;i<n;i++)
{
p=q;
q=r;
r=p+q;
}
return r;
}
};
- 我自己的另一种写法
class Solution {
public:
int climbStairs(int n) {
//动态规划
//初始值,边界条件
int f0=1,f1=1,fn=0;
if(n==0) return f0;
if(n==1) return f1;
//迭代
while(n>1)
{
fn=f0+f1;
f0=f1;
f1=fn;
n--;
}
return fn;
}
};
121. 买卖股票的最佳时机
给定一个数组 prices
,它的第i
个元素 prices[i]
表示一支给定股票第 i
天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0
。
思路:
- 利用动态规划思想,核心是要先找一个最低价格,然后再找一个它之后的最高价格(如果有的话),得出最大利润
- 用两个变量
min
和max
来维护当前的最低价格和最大利润 - 最大利润由当前价格减去当前最低价格得到
- 总结起来就是—— 前i天的最大收益 =
max{前i-1天的最大收益,第i天的价格-前i-1天中的最小价格}
(来自评论)
代码
class Solution {
public:
int maxProfit(vector<int>& prices) {
//动态规划,其实也利用了双指针
int minprice = 1e9, maxprofit = 0;
for (int price: prices) {
maxprofit = max(maxprofit, price - minprice);
//第i天的价格-前i-1天中的最小价格
minprice = min(price, minprice);
}
return maxprofit;
}
};
53. 最大子序和
给定一个整数数组nums
,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
思路:
- 害,我真的不太会用动态规划
- 这道题的关键应该是在dp数组中存放以
nums[i]
为结尾的子数组的和,但是存放的并不是从nums[0]
到nums[i]
的子数组的和 - 在计算
dp[i]
的值时,应考虑dp[i-1]
是否为正,如果为正的话就进行累加,加上nums[i]
作为一个新的子数组和,如果为负的话,加到nums[i]
上会导致越加越小,因此舍弃掉之前的子数组,从nums[i]
重新开始加 - 利用一个
dp
变量滚动地记录以nums[i]
结尾的子数组和 - 再用一个
maxNum
来存储最大的子数组和
代码
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int size=nums.size(),maxNum,dp=nums[0];
//最大值变量
maxNum=nums[0];
//遍历nums,看前一个dp是否大于0,如果大于0,就进行累加
//否则就丢弃它,不然会越加越小
for(int i=1;i<size;i++)
{
dp=nums[i]+max(dp,0);
maxNum=max(dp,maxNum);
}
return maxNum;
}
};
198.打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
思路:
-
(官方题解)
-
首先考虑最简单的情况。如果只有一间房屋,则偷窃该房屋,可以偷窃到最高总金额。如果只有两间房屋,则由于两间房屋相邻,不能同时偷窃,只能偷窃其中的一间房屋,因此选择其中金额较高的房屋进行偷窃,可以偷窃到最高总金额。
-
如果房屋数量大于两间,应该如何计算能够偷窃到的最高总金额呢?对于第
k~(k>2)
间房屋,有两个选项:
- 偷窃第
k
间房屋,那么就不能偷窃第k-1
间房屋,偷窃总金额为前k-2
间房屋的最高总金额与第k
间房屋的金额之和。 - 不偷窃第
k
间房屋,偷窃总金额为前k-1
间房屋的最高总金额。
- 在两个选项中选择偷窃总金额较大的选项,该选项对应的偷窃总金额即为前
k
间房屋能偷窃到的最高总金额。
用 dp[i] 表示前 i 间房屋能偷窃到的最高总金额,那么就有如下的状态转移方程:
dp[i]=max(dp[i-2]+nums[i],nums[i-1])
- 最终答案为
dp[n-1]
- 本题利用动态数组能节省更多空间
代码
class Solution {
public:
int rob(vector<int>& nums) {
/*
int size=nums.size();
vector<int>dp(size,0);
dp[0]=nums[0];
if(size>1)
dp[1]=max(nums[0],nums[1]);
//偷还是不偷,是个问题
for(int i=2;i<size;i++)
{
dp[i]=max(dp[i-2]+nums[i],dp[i-1]);
}
return dp[size-1];
*/
//利用滚动数组
int size=nums.size();
int p=nums[0],q;
if(size>1)
q=max(nums[0],nums[1]);
for(int i=2;i<size;i++)
{
int temp=q;
q=max(p+nums[i],q);
p=temp;
}
if(size==1)
return p;
else
return q;
}
};
279. 完全平方数
给定正整数 n
,找到若干个完全平方数(比如 1, 4, 9, 16, ...
)使得它们的和等于 n
。你需要让组成和的完全平方数的个数最少。
给你一个整数 n
,返回和为 n
的完全平方数的 最少数量 。
完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16
都是完全平方数,而 3 和 11
不是。
动态规划
思路:
- 对于正整数
n
,它由若干个完全平方数相加得到,那么n
应该满足n=a*a+b
,a*a
就代表其中的一个平方数。 b=n-a*a
同样也是由若干个完全平方数相加得到,求得了组成b
的最少完全平方数的个数就能得到n
的。- 利用动态规划,大问题可以逐个分解成很多小问题解决。
- 构建动态规划数组
dp
,从小到大计算每个正整数i
的最小完全平方数个数。
代码
class Solution {
public:
int numSquares(int n) {
vector<int>dp(n+1,0);
int minVal;
for(int i=1;i<=n;i++)
{
minVal=INT_MAX;
for(int j=1;j*j<=i;j++)
{
minVal=min(minVal,dp[i-j*j]);
}
dp[i]=minVal+1;
}
return dp[n];
}
};