动态规划
509.Fibonacci numbers
O ( 2 n ) O(2^n) O(2n)
int fib(int n){
return n <= 1 ? n : fib(n - 1) + fib(n - 2);
}
O ( n ) O(n) O(n)
class Solution {
public:
int fib(int N) {
vector<int> dp(N+2,0);
dp[0] = 0;
dp[1] = 1;
if(N >= 2){
for(int i = 2;i <= N;i++){
dp[i] = dp[i-1] + dp[i-2];
}
}
return dp[N];
}
};
更好的方法:
class Solution {
public:
int fib(int n) {
if(n <= 1) return n;
int a = 0, b = 1,c = 0;
for(int i = 2; i <= n; ++i){
c = a + b;
a = b;
b = c;
}
return c;
}
};
70. Climbing Stairs
You are climbing a stair case. It takes n steps to reach to the top.
Each time you can either climb 1 or 2 steps. In how many distinct wayscan you climb to the top?
Note: Given n will be a positive integer.
Input: 3
Output: 3
Explanation: There are three ways to climb to the top.
- 1 step + 1 step + 1 step
- 1 step + 2 steps
- 2 steps + 1 step
找出递推公式 f ( n ) = f ( n − 1 ) + f ( n − 2 ) f(n) = f(n-1)+f(n-2) f(n)=f(n−1)+f(n−2),发现就是斐波那契数列,解法同上。
120. Triangle
Given a triangle, find the minimum path sum from top to bottom. Eachstep you may move to adjacent numbers on the row below.
The minimum path sum from top to bottom is 11 (i.e., 2 + 3 + 5 + 1 = 11).
- 暴力法(DFS) O ( 2 n ) O(2^n) O(2n),在超大用例下会超内存。
#include <iostream>
#include <vector>
using namespace std;
class Solution
{
public:
vector<vector<int>> ans;
int minimumTotal(vector<vector<int>> &triangle){
vector<int> path(triangle.size(),0);//初始化棋盘
path[0] = {triangle[0][0]};
helper(1,0,path,triangle);
int min = INT32_MAX;
for(auto i : ans){
int sum = 0;
for(auto j : i){
sum += j;
}
if(sum < min){
min = sum;
}
}
return min;
}
void helper(int level,int pos,vector<int> &path,vector<vector<int>> &triangle){
if(level == triangle.size()) {
ans.push_back(path);
return;
}
path[level] = triangle[level][pos];
helper(level+1,pos,path,triangle);
path[level] = triangle[level][pos+1];
helper(level+1,pos+1,path,triangle);
}
};
void outPut(vector<vector<int>> &triangle){
for(auto i : triangle){
for(auto j : i){
cout<<j<<" ";
}
cout<<endl;
}
}
int main()
{
vector<vector<int>> tri;
tri.push_back({2});
tri.push_back({3,4});
tri.push_back({6,5,7});
tri.push_back({4,1,8,3});
Solution s;
cout<<s.minimumTotal(tri)<<endl;
outPut(s.ans);
getchar();
return 0;
}
其实只需要记录路径和,不需要求所有路径,所以程序还能优化。
static int min=INT_MAX ;
int DFS(int** t,int row,int col,int cur,int RowSize){//深度优先来穷举,O(2的n次方)超时
if(row==RowSize){
min=cur<min?cur:min;
return 1;
}
cur+=*(*(t+row)+col);
DFS(t,row+1,col,cur,RowSize);
DFS(t,row+1,col+1,cur,RowSize);
return min;
}
- DP
O
(
n
)
O(n)
O(n)
- 定义状态:DP[i][j]表示从底部到tri[i][j]的最短距离,那么DP[0][0]为题解。
- 写出方程: D P [ i ] [ j ] = m i n ( D P [ i + 1 ] [ j ] , D P [ i + 1 ] [ j + 1 ] ) + t r i [ i ] [ j ] DP[i][j]=min(DP[i+1][j],DP[i+1][j+1])+tri[i][j] DP[i][j]=min(DP[i+1][j],DP[i+1][j+1])+tri[i][j]
这里注意一定要给DP数组初始化,且其大小稍大于实际空间,不然会出bug:
int minimumTotal(vector<vector<int>> &triangle)
{
int last = triangle.size() - 1;
//记住下面的初始化方法
vector<vector<int>> DP(last+1,vector<int>(last+1,0));
DP[last] = triangle[last];
for(int i = last - 1;i >= 0;i--){
for(int j = 0;j < triangle[i].size();j++){
DP[i][j] = min(DP[i+1][j],DP[i+1][j+1]) + triangle[i][j];
}
}
return DP[0][0];
}
53. 最大子序和(经典题)
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
状态方程如下(将就看看。。)
int maxSubArray(vector<int>& nums) {
int DP[nums.size()];
DP[0] = nums[0];
int m=DP[0];
for(int i=1;i<nums.size();i++){
DP[i] = max(DP[i-1]+nums[i],nums[i]);
m=DP[i]<=m?m:DP[i];
}
return m;
}
又由于dp备忘录数据不需要保存,只会用到最后一个值,所以可以将dp数组变为一个变量。优化很大。
int maxSubArray(vector<int>& nums) {
int DP = nums[0];
int m=DP;
for(int i=1;i<nums.size();i++){
DP = max(DP+nums[i],nums[i]);
m=DP<=m?m:DP;
}
return m;
}
152.乘积最大子序列
- 暴力,求出所有子序列,再算出他们的值。
- DP
- 定义状态:DP[0][i]表示第i个元素为子序列末尾元素时,的最大正乘积。DP[1][i]表示第i个元素为子序列末尾元素时,的最大负乘积。所以题解为DP数组中所有元素最值
- 写出方程:
- A[i]大于0时,DP[0][i] = DP[0][i-1]*A[i]、DP[1][i] = DP[1][i-1]*A[i]
- A[i]小于0时,DP[0][i] = DP[1][i-1]*A[i] 、DP[1][i] = DP[0][i-1]*A[i]
- A[I]等于0时,不知道怎么办了。看了下评论区知道用max、min函数是坠吼的
- 重新写方程:
D P [ 0 ] [ i ] = m y M a x ( D P [ 0 ] [ i − 1 ] ∗ n u m s [ i ] , D P [ 1 ] [ i − 1 ] ∗ n u m s [ i ] , n u m s [ i ] ) ; D P [ 1 ] [ i ] = m y M i n ( D P [ 0 ] [ i − 1 ] ∗ n u m s [ i ] , D P [ 1 ] [ i − 1 ] ∗ n u m s [ i ] , n u m s [ i ] ) ; DP[0][i] = myMax(DP[0][i-1]*nums[i],DP[1][i-1]*nums[i],nums[i]);\\ DP[1][i] = myMin(DP[0][i-1]*nums[i],DP[1][i-1]*nums[i],nums[i]); DP[0][i]=myMax(DP[0][i−1]∗nums[i],DP[1][i−1]∗nums[i],nums[i]);DP[1][i]=myMin(DP[0][i−1]∗nums[i],DP[1][i−1]∗nums[i],nums[i]);
class Solution {
public:
int myMax(int a,int b,int c){
return max(max(a,b),c);
}
int myMin(int a,int b,int c){
return min(min(a,b),c);
}
int maxProduct(vector<int>& nums) {
int max = INT32_MIN;
vector<vector<int>> DP(2,vector<int>(nums.size(),0));
DP[1][0] = DP[0][0] = nums[0];
for(int i = 1;i<nums.size();i++){
DP[0][i] = myMax(DP[0][i-1]*nums[i],DP[1][i-1]*nums[i],nums[i]);
DP[1][i] = myMin(DP[0][i-1]*nums[i],DP[1][i-1]*nums[i],nums[i]);
}
for(auto i : DP[0]){
max = max>i?max:i;
}
return max;
}
};
买卖股票系列
题号 | 买卖次数 |
---|---|
122 | 无数次 |
121 | 1次 |
123 | 2次 |
188 | k次 |
309 | 买卖之间有cd |
714 | 每次买卖收手续费 |
无数次
- 定义状态
max_profit二维数组作为DP数组。MP[i][j]表示到第i天结束时已经赚取的最大利润,此时j=0表示未持股,j=1表示持股。 - 写出状态转移方程
- MP[i][0] = max(MP[i-1][0],MP[i-1][1]+nums[i])
- MP[i][1] = max(MP[i-1][1],MP[i-1][0]-nums[i])
执行用时 :8 ms, 在所有 C++ 提交中击败了72.96%的用户
内存消耗 :11.9 MB, 在所有 C++ 提交中击败了5.03%的用户
int maxProfit(vector<int>& prices) {
if(prices.size()<=1) return 0;
int len = prices.size();
vector<vector<int>> MP(len,vector<int>(2,0));
MP[0][1] =-prices[0];//这里注意下初值
MP[0][0] = 0;
for(int i = 1;i < len;i++){
MP[i][0] = max(MP[i-1][0],MP[i-1][1]+prices[i]);
MP[i][1] = max(MP[i-1][1],MP[i-1][0]-prices[i]);
}
return MP[len-1][0];
}
有限次
用DP解问题时,如果发现很难写出状态方程, 那么尝试加一个状态。本题用MP[i][j][k]中的k表示已经买卖了k次。注意只有买入时才算一次交易,卖出时不算交易次数。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int K=2;//或K=2解决121题
if(prices.size()<=1) return 0;
int len = prices.size();
vector<vector<vector<int>>> MP(len,vector<vector<int>>(K+1,vector<int>(2,0)));
for(int k = K;k>0;k--){
MP[0][k][1] =-prices[0];
MP[0][k][0] = 0;
}
for(int i = 1;i < len;i++){
for(int k = K;k > 0;k--){
MP[i][k][0] = max(MP[i-1][k][0],MP[i-1][k][1]+prices[i]);
MP[i][k][1] = max(MP[i-1][k][1],MP[i-1][k-1][0]-prices[i]);
}
}
return MP[len-1][K][0];
}
};
现在已经解决了一次、两次、无数次的题。K次的出bug,还有其他的题挖坑,需要学习这篇文章。
376 摆动序列
由于情况特殊,可以用贪心做:
int wiggleMaxLength(vector<int> &nums)
{
if (nums.size() < 2)
return nums.size();
int p = 0;
//去除重复元素
while (p < nums.size() - 1 and nums[p + 1] == nums[p])
{
p++;
}
if (p == nums.size() - 1)
return 1;
int res;
bool f = nums[p + 1] > nums[p]; //求初始方向
p++;
int cnt = 2;
while (p < nums.size() - 1)
{
res = nums[p + 1] - nums[p];
if (f)
{
if (res < 0)
{
cnt++;
f = !f;
}
}
else
{
if (res > 0)
{
cnt++;
f = !f;
}
}
p++;
}
return cnt;
}
这题是贪心的题,用DP做思维上麻烦了,代码上却有种对称的美。参见我做的最长公共子序列这道题,定义应该是这样:
- dp[i][0]表示以第i个元素结尾且期望匹配比尾值大的值时的最大值
- dp[i][1]表示以第i个元素结尾且期望匹配比尾值小的值时的最大值
- dp值为0表示不可能有这种情况。比如1 2这个序列,dp[1][1]应该为0
解释:
- 分析题意,结果必然是将第一个元素作为子序列开头元素,所以无需额外维度保存起始位置。
- 但由于状态方程向后递推,必须知道此时尾节点是哪个才能推出下一个的值,所以需要一个维度记录尾节点位置。
- 由于本题的特殊性,需要一个维度记录当前欲匹配元素类型。
方程应该这样写:(有点难写啊。。写了几个小时)
int wiggleMaxLength(vector<int> &nums)
{
if (nums.size() == 0)
return 0;
vector<vector<int>> dp(nums.size(), vector<int>(2, 0));
dp[0][0] = dp[0][1] = 1;
for (int i = 1; i < nums.size(); i++)
{
dp[i][0] = nums[i - 1] > nums[i] ? max(dp[i - 1][1] + 1, dp[i - 1][0]) : 0;
dp[i][1] = nums[i - 1] < nums[i] ? max(dp[i - 1][0] + 1, dp[i - 1][1]) : 0;
if (nums[i - 1] == nums[i])
{
dp[i][0] = dp[i - 1][0];
dp[i][1] = dp[i - 1][1];
}
}
return max(dp[nums.size() - 1][0], dp[nums.size() - 1][1]);
}
为什么状态方程中有个max?想想1212123这个序列,匹配到3时有两种选择
- 放弃之前的结果,选择将2和3放进结果中,加1:
dp[i - 1][1] + 1 = 2
- 保留之前的结果,放弃3:
dp[i - 1][0] = 6
肯定是选择2赚。那如果是12322呢,就应该放弃之前的结果,选择232这个序列也就是方法一。所以要max函数求最大值。
如果是最长子串(连续子序列)的话,b[i]=b[i-1]时等于1即可。