文章目录
- 爬楼梯
- 题意:
- 解:
- 代码:
- 买卖股票的最佳时机
- 题意:
- 解:
- 代码:
- 最大子序和
- 题意:
- 解:
- 代码:
- 打家劫舍
- 题意:
- 解:
- 代码:
爬楼梯
题意:
从0开始,每次+1或者+2,求和为n有多少种可能
解:
DP[i]
表示到达i
能有几种可能
初始状态DP[0]=1
递推公式DP[i]=DP[i-1]+DP[i-2]
因为每次只能+1或+2,所以而状态i-1
一定不会影响(改变)i-2
所以正向遍历即可(i-2
会改变i-1
)
代码:
#include<iostream>
#include<cstring>
using namespace std;
constexpr int N=50;
int climbStairs(int n)
{int dp[N];memset(dp,0,sizeof dp);dp[0]=1;for(int i=0;i<n;i++){dp[i+1]+=dp[i];dp[i+2]+=dp[i];}return dp[n];
}
int main()
{int ans;cin>>ans;ans=climbStairs(ans);cout<<ans<<endl;
}
买卖股票的最佳时机
题意:
预知股票价格,求一次买入+一次卖出能赚的最大金额
解:
DP[i]
表示第i
天前的最低价格
对于第Day天的价格我们要找Day之前最便宜的股票买,所以递推式DP[day]=min{ Day[0,Day) }
而初始状态第一天之前的价格我们设置成>=prices[0]
即可,因为这时候是不赚钱甚至亏钱的,保持答案0
那么对于每一天day我们先用今天的价格prices[day]-DP[day-1]
获取今天的最高利润,然后DP[day]=min(prices[day],DP[day-1])
更新这一天之前(包含)的最低价格
由于是一次买入一次卖出,所以取最高值,并且DP[i]只和DP[i-1]有关,而这两者都是基于prices[i]计算/更新,
所以用一个变量aftre充当DP[i-1]即可
代码:
#include<bits/stdc++.h>
using namespace std;
int maxProfit(vector<int>& prices)
{int lg=prices.size();int after=INT_MAX,ans=0;for(auto &price:prices){ans=max(ans,price-after);after=min(after,price);}return ans;
}
int main()
{vector<int> prices;int temp;while(cin>>temp){prices.push_back(temp); }int ans=maxProfit(prices);cout<<ans<<endl;return 0;
}
最大子序和
题意:
如题。子数组 是数组中的一个连续部分。
解:
主要说一下分治法
思路就是一个区间[l,r]
内的最大子数组要么处于[l,mid]
要么处于[mid+1,r]
,或者处于[l,r]
,虽然很像废话,但是对计算机来说不一样,由于要找子数组,所以要保持连续性,所以当这个区间内的最大子数组在[l,r]
情况下,一定是包含[l,mid]
的右边和[mid+1,r]
的左边
那么对于一个区间,我们维护它的左起最大子数组、右起最大子数组还有无条件子数组以及区间和
分别有什么作用呢?对于最小子区间[t,t]
这四个数值都是nums[t]
而对于一个父区间,它的左起子数组 是max [l,mid]->left ,[mid+1,r]->left+[l,mid]->sum
,为了保持连续性,只有两种可能,要么是左子区间的 左起最大子数组,要么是整个左子区间加上右子区间的 左起最大子数组
对于右起最大子数组也是一样的,保留右起的连续性;
还有一种无条件子数组,指的是不一定包含左起点或者右起点的最大子数组,由于这可能是答案的一部分,所以要对它进行保留和计算,计算方式是<左子区间的右连上右子区间的左>和两个子区间的无条件子数组中取最大值
代码:
#include<bits/stdc++.h>
using namespace std;
struct ITEM
{int l,r,m,sum;
};
ITEM query(int l,int r,vector<int>& nums)
{//cout<<"l:"<<l<<"r"<<r<<endl;if(l==r) return {nums[l],nums[l],nums[l],nums[l]};int mid=(l+r)>>1;ITEM item1=query(l,mid,nums);ITEM item2=query(mid+1,r,nums);ITEM ret;ret.l=max(item1.l,item1.sum+item2.l);ret.r=max(item2.r,item2.sum+item1.r);ret.m=max(item2.l+item1.r,max(item1.m,item2.m));ret.sum=item1.sum+item2.sum;return ret;
}
int maxSubArray(vector<int>& nums)//分治法
{int lg=nums.size();ITEM ret=query(0,lg-1,nums);int ans=max(ret.l,max(ret.r,ret.m));return ans;
}
/*
int maxSubArray(vector<int>& nums)//遍历法O(n)
{int lg=nums.size(),temp=0,ans=nums[0];for(auto &num:nums){temp+=num;ans=max(ans,temp);if(temp<0) temp=0;}return ans;
}*/
int main()
{vector<int> nums;int num;while(cin>>num) nums.push_back(num);int ans=maxSubArray(nums);cout<<ans<<endl;return 0;
}
打家劫舍
题意:
一个非负整数数组,从中选择数字字求最大和,要求不能选择连续的数字
解:
先将问题想的复杂一点,DP[0][i]
表示第i个数字不选择时区间[0,i]
的最大和,DP[1][i]
表示选择第i个数字,这样可轻松推出dp[0][i]=max(dp[0][i-1],dp[1][i-1])
和dp[1][i]=max(nums[i]+dp[0][i-1],dp[0][i-1])
但是我们再想一想,DP[i]
表示第i个数字(不管选不选)时区间[0,i]
的最大和,那么由于不知道i是否被选上,所以我们的递推式只能变成DP[i]=max(DP[i-1],DP[i-2]+nums[i])
,这样是否合理呢?
是合理的因为DP[i]
不管选没选择这个递推式都不会选择连续的数字,只要初始状态合理就行,初始状态设置DP[0]=nums[0] DP[1]=max(nums[0],nums[1])
,从第一步DP[2]的计算我们就可以看出,无论如何我们都不会计算DP[2]包含nums[1]+nums[2]
,同时由于这个递推式只需要三个变量,所以可以用滚动变量解决
代码:
#include<bits/stdc++.h>
using namespace std;
int rob(vector<int>& nums)//J2
{int lg=nums.size();if(lg==1) return nums[0];int temp1=nums[0],temp2=max(nums[0],nums[1]);for(int i=2;i<lg;i++){int temp=temp2;temp2=max(temp2,temp1+nums[i]);temp1=temp;}return temp2;
}
/*
int rob(vector<int>& nums)//J1
{int lg=nums.size();vector<vector<int>>dp(2,vector<int>(lg));dp[1][0]=nums[0];for(int i=1;i<lg;i++){dp[0][i]=max(dp[0][i-1],dp[1][i-1]);dp[1][i]=max(nums[i]+dp[0][i-1],dp[0][i-1]);}return max(dp[0][lg-1],dp[1][lg-1]);
}*/
int main()
{vector<int> nums;int num;while(cin>>num) nums.push_back(num);int ans=rob(nums);cout<<ans<<endl;return 0;
}