只是简单记录刷题的历程(大佬们勿喷 )
https://leetcode-cn.com/tag/dynamic-programming/(动态规划专题集)
简单题
爬楼梯
https://leetcode-cn.com/problems/climbing-stairs/
思路: 到达第 n 阶有两种方法,n-2阶后爬 2 阶,或者 n-1 阶后爬 1 阶,最后计算这两种方法的总和,因此我们只需关注 n-2 和 n-1阶的方法数,同理,逐渐往下推导,直到 "n-1""n-2"是第0阶台阶,即不可再划分为止。而动态规划则是将思路自底向上计算的过程,在 dp[0]=1,dp[2]=2的基础上利用状态方程dp[i] = dp[i-1] + dp[i-2]逐级递推 (或者是不用数组 f(n) = f(n-1) + f(n-2),此时 f1 = 1,f2 = 2),最后一个数 dp[i] (或是f(n) )即为所求
class Solution {
public:
int climbStairs(int n) {
int sum = 0;
if(n<3)
return n;
int f1 = 1,f2 = 2;
for(int i = 3; i<=n;++i){
sum = f1+f2;
f1 = f2;
f2 = sum;
}
return sum;
}
};
除数博弈
https://leetcode-cn.com/problems/divisor-game/
可能看的时候会以为很复杂,结果代码就一行www(可以用动态规划解题,但是知道原理似乎就不是很必要)
原理:奇数的因子都是奇数,所以当A玩家起初拿到的是奇数,进行 N-x操作后,B玩家拿到的就是偶数,那么每次B玩家都只要进行 N-1操作后,使得 A玩家都只能拿到 奇数,而自己最后就能得到 2这个最小偶数,则 B必然可以赢,即A必然输。
相反,若A起初拿到的是偶数,则和上述情况相反,A必然赢
因此,只需判断 N的奇偶性便可以得到最终结果
class Solution {
public:
bool divisorGame(int N) {
return N%2==0?true:false;
}
};
最大子序和
https://leetcode-cn.com/problems/maximum-subarray/
思路:以第i个数结尾的子序列的最大和满足递推关系dp= max(dp+nums[i],nums[i]);也就是如果前面 dp是负数,nums[i]是正数,丢弃前面的 dp ,重新开始新一段连续子序列;但如果 nums[i]是负数,dp是正数,则继续往下+ nums[i],但是最大和保持没加 nums[i]之前的,直到下一次更新(可能解释得不是很清楚www)
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int dp = -1;
int res = INT_MIN;
//以第i个数结尾子数组的最大值
for(int i = 0;i<nums.size(); i++){
dp= max(dp+nums[i],nums[i]);//负数丢弃
res = max(res,dp);//更新当前最大和
}
return res;
}
};
买卖股票的最佳时机
思路:记录今天买入之前的最小值–>计算第 i 天卖出的获利(第i 天股票的价钱 - 最小值) -->比较每天的获利
class Solution {
public:
int maxProfit(vector<int>& prices) {
int res = 0;
int dp = INT_MAX;
for(int i = 0 ;i<prices.size();i++){
dp = min(prices[i],dp);//记录今天买入之前的最小值
res = max(prices[i]-dp,res);//比较每天的获利
}
return res;
}
};
打家劫舍
https://leetcode-cn.com/problems/house-robber/
思路:比较当天偷和和当天不偷的获利
dp[i-2]+nums[i]表示当天可以偷,dp[i-1]表示当天不偷的获利,则最后一次记录的 dp[n-1]则为最大值
边界状态 :n <=0(获利 = 0); n = 1(没得选);n = 2(挑金额最大的一间房间)
class Solution {
public:
int rob(vector<int>& nums) {
int n = nums.size();
vector<int> dp(n);
if(n<=0)
return 0;
else if(n<=2){
return n==2?max(nums[0],nums[1]):nums[0];
}
dp[0] = nums[0];
dp[1] = max(nums[0],nums[1]);
for(int i = 2; i<n;i++){
dp[i] = max(dp[i-1],dp[i-2]+nums[i]);
}
return dp[n-1];
}
};
另一种写法,思路一致,只是可以不用开数组,代码更简单
class Solution {
public:
int rob(vector<int>& nums) {
int n = nums.size();
int f1 = 0,f2 = 0;
int temp = 0;
for(int i = 0; i<n;i++){
temp = f1;
f1 = max(f1,f2+nums[i]);
f2 = temp;
}
return f1;
}
};
区域和检索 - 数组不可变
https://leetcode-cn.com/problems/range-sum-query-immutable/
思路:虽然本题的标签是动态规划,但其实是前缀和(题目提到 会多次调用 sumRange 方法,前缀和正是解决多次调用的一种有效方法)
ps: 记录一下 C++ 中vector 的 resize 和 reserve函数的区别(大佬们可以忽略 )
resize函数是重新分配 vector 的实际大小(即包含是元素数量),而 reserve是重新分配 vector 容量(可以理解为容量是提前先预留的房子,size是实际住的人数)
class NumArray {
public:
vector<int>dp;
NumArray(vector<int>& nums) {
dp.resize(nums.size(),0);
if(!nums.empty()){
dp[0] = nums[0];
for(int k = 1; k<nums.size();k++){
dp[k] = dp[k-1]+nums[k];
}
}
}
int sumRange(int i, int j) {
if(i==0){
return dp[j];
}else
return dp[j]-dp[i-1];
}
};
使用最小花费爬楼梯
https://leetcode-cn.com/problems/min-cost-climbing-stairs/
思路:这道题有一个坑点,数组的最后一个元素还没到阶梯顶,应该在数组后面再加一个 0 ,否则在算最小花费的时候会造成把数组的最后一个元素花费跟倒数第二个花费没法比较
动态规划思路: 因为到第n层有两种方法,从 n-2层跳两步或从 n-1层跳一步,所以只需要比较这两层的花费即可
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost) {
cost.push_back(0);
int len = cost.size();
vector<int> dp(len);
for(int i = 2; i<len; i++){
cost[i] += min(cost[i-1],cost[i-2]);
}
return cost.back();
}
};