[LEECODE]算法进阶自练习17 动态规划
17.动态规划
简单
- 爬楼梯 leetcode 70 递归解法,不能AC
int climbStairs(int n) {
if(n == 1) return 1;
if(n == 2) return 2;
return climbStairs(n-1) + climbStairs(n-2);
}
循环解法:类似求斐波那契数列
int climbStairs(int n) {
int s1 = 0, s2 = 0, ret = 1;
for(int i=1; i<=n; i++){
s1 = s2;
s2 = ret;
ret = s1 + s2;
}
return ret;
}
动态规划:存储所有的状态集合
int climbStairs(int n) {
if(n == 1) return 1;
if(n == 2) return 2;
vector<int> ret(n, 0);
ret[0] = 1;
ret[1] = 2;
for(int i=2; i<n; i++){
ret[i] = ret[i-1] + ret[i-2];
}
return ret[n-1];
}
- 打家劫舍 leetcode 198 动态规划解法:
int rob(vector<int>& nums) {
int nsize = nums.size();
if(nsize == 0) return 0;
if(nsize == 1) return nums[0];
vector<int> ret(nsize,0);
ret[0] = nums[0]; ret[1] = max(nums[0],nums[1]);
for(int i=2; i<nsize; i++){
ret[i] = max(ret[i-1],ret[i-2] + nums[i]);
}
return ret[nsize-1];
}
滚动数组,减少空间复杂度解法:
int rob(vector<int>& nums) {
int nsize = nums.size();
if(nsize == 0) return 0;
if(nsize == 1) return nums[0];
int first = nums[0], second = max(nums[0],nums[1]);
for(int i=2; i<nsize; i++){
int temp = second;
second = max(first+nums[i], second);
first = temp;
}
return second;
}
中等
- 最佳买卖股票时机含冷冻期 leetcode 309 动态规划解法:
int maxProfit(vector<int>& prices) {
int psize = prices.size();
if(psize < 2) return 0;
// dp[i][0]:持有股票最大收益
// dp[i][1]:持有股票但是在冷冻期最大收益
// dp[i][2]:不持有股票,且不在冷冻期的最大收益
vector<vector<int>> dp(psize, vector<int>(3,0));
dp[0][0] = -prices[0];
for(int i=1; i<psize; i++){
dp[i][0] = max(dp[i-1][0], dp[i-1][2] - prices[i]);
dp[i][1] = dp[i-1][0] + prices[i]; // 不能再买,但是可以再卖
dp[i][2] = max(dp[i-1][1], dp[i-1][2]);
}
return max(dp[psize-1][1], dp[psize-1][2]);
}
动态规划状态压缩解法:
int maxProfit(vector<int>& prices) {
int psize = prices.size();
if(psize < 2) return 0;
// dp0:持有股票最大收益
// dp1:持有股票但是在冷冻期最大收益
// dp2:不持有股票,且不在冷冻期的最大收益
int dp0 = -prices[0];
int dp1 = 0;
int dp2 = 0;
for(int i=1; i<psize; i++){
int n_dp0 = max(dp0, dp2 - prices[i]);
int n_dp1 = dp0 + prices[i]; // 不能再买,但是可以再卖
int n_dp2 = max(dp1, dp2);
dp0 = n_dp0;
dp1 = n_dp1;
dp2 = n_dp2;
}
return max(dp1, dp2);
}
- 打家劫舍II leetcode 213
int myRob(vector<int>& nums){
int nsize = nums.size();
if(nsize == 0) return 0;
if(nsize == 1) return nums[0];
int first = nums[0], second = max(nums[0], nums[1]);
for(int i=2; i<nsize; i++){
int tmp = second;
second = max(second, first + nums[i]);
first = tmp;
}
return second;
}
int rob(vector<int>& nums) {
int nsize = nums.size();
if(nsize == 0) return 0;
if(nsize == 1) return nums[0];
vector<int> nums1(nums.begin(), nums.end()-1), nums2(nums.begin()+1, nums.end());
return max(myRob(nums1),myRob(nums2));
}
- 打家劫舍III leetcode 337 这里问题其实就转换成为在节点只存在选中与不选中的情况下,不能同时选中父子节点的最大权值是多少,因此这里其实也可以用动态规划。
class Solution {
public:
unordered_map<TreeNode*, int> sel, un_sel;
void dfs(TreeNode* node){
if(!node){
return;
}
dfs(node->left);
dfs(node->right);
sel[node] = node->val + un_sel[node->left] + un_sel[node->right]; // 选中当前node节点,那么当前节点的左右子节点就不能选中了。
un_sel[node] = max(sel[node->left], un_sel[node->left]) + max(sel[node->right], un_sel[node->right]); // 不选中当前子节点,那么就需要判断左子树和右子树中选中与不选中的最大值。
}
int rob(TreeNode* root) {
dfs(root);
return max(sel[root],un_sel[root]);
}
};
优化一下存储空间:
struct SelStatus{
int sel;
int unsel;
};
class Solution {
public:
SelStatus dfs(TreeNode* node){
if(!node){
return {0,0};
}
SelStatus left = dfs(node->left);
SelStatus right = dfs(node->right);
int sel = node->val + left.unsel + right.unsel;
int unsel = max(left.unsel, left.sel) + max(right.sel, right.unsel);
return {sel, unsel};
}
int rob(TreeNode* root) {
SelStatus status = dfs(root);
return max(status.sel, status.unsel);
}
};
- 不同路径 leetcode 62 动态规划解法:
int uniquePaths(int m, int n) {
vector<vector<int>> dp(m+1,vector<int>(n+1,0));
for(int i=0; i<m; i++) for(int j=0; j<n; j++) dp[i][j] = (i==0 || j==0) ? 1 : (dp[i-1][j] + dp[i][j-1]);
return dp[m-1][n-1];
}
排列组合解法:
int uniquePaths(int m, int n) {
if(m==1 || n==1) return 1;
if(m>n) swap(m,n);
unsigned long t = 1, ret = 1;
for(int i=1; i<=m-1; i++) t*=i;
for(int i=n; i<=m+n-2; i++) ret*=i;
return ret/t;
}
- 不同路径II leetcode 63 动态规划解法:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
int m = obstacleGrid.size(), n = obstacleGrid[0].size();
vector<vector<int>> dp(m,vector<int>(n,0));
for (int i = 0; i < m && obstacleGrid[i][0] == 0; i++) {
dp[i][0] = 1;
}
for (int j = 0; j < n && obstacleGrid[0][j] == 0; j++) {
dp[0][j] = 1;
}
for(int i=1; i<m; i++){
for(int j=1; j<n; j++){
if(obstacleGrid[i][j] == 0){
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
}
return dp[m-1][n-1];
}
困难
- 买卖股票的最佳时机 IV leetcode 188
int infMaxProfit(vector<int>& prices){
int psize = prices.size();
if(psize < 2) return 0;
int ret = 0;
for(int i=0; i<psize-1; i++){
if(prices[i+1] > prices[i]) ret += (prices[i+1] - prices[i]);
}
return ret;
}
int maxProfit(int k, vector<int>& prices) {
int psize = prices.size();
if(psize < 2 || k < 1) return 0;
if(k > psize/2) return infMaxProfit(prices);
// k次交易
// 0 买入股票 1卖出股票
vector<vector<int>> dp(k, vector<int>(2,INT_MIN));
for(int j=0; j<psize; j++){
dp[0][0] = max(dp[0][0], -prices[j]); // 第一次买
dp[0][1] = max(dp[0][1], dp[0][0] + prices[j]); // 第一次卖
for (int i = 1; i < k; i++)
{
dp[i][0] = max(dp[i][0], dp[i - 1][1] - prices[j]); // 第 i 次买
dp[i][1] = max(dp[i][1], dp[i][0] + prices[j]); // 第 i 次卖
}
}
return dp[k-1][1];
}
- 买卖股票的最佳时机 III leetcode 123
下面的题解加个
k=2就可以了:
int infMaxProfit(vector<int>& prices){
int psize = prices.size();
if(psize < 2) return 0;
int ret = 0;
for(int i=0; i<psize-1; i++){
if(prices[i+1] > prices[i]) ret += (prices[i+1] - prices[i]);
}
return ret;
}
int maxProfit(vector<int>& prices) {
int psize = prices.size();
if(psize < 2) return 0;
int k = 2;
if(k > psize/2) return infMaxProfit(prices);
// k次交易
// 0 买入股票 1卖出股票
vector<vector<int>> dp(k, vector<int>(2,INT_MIN));
for(int j=0; j<psize; j++){
dp[0][0] = max(dp[0][0], -prices[j]); // 第一次买
dp[0][1] = max(dp[0][1], dp[0][0] + prices[j]); // 第一次卖
for (int i = 1; i < k; i++)
{
dp[i][0] = max(dp[i][0], dp[i - 1][1] - prices[j]); // 第 i 次买
dp[i][1] = max(dp[i][1], dp[i][0] + prices[j]); // 第 i 次卖
}
}
return dp[k-1][1];
}
三维dp解法:
int maxProfit(vector<int>& prices) {
int psize = prices.size();
if(psize < 2) return 0;
// 第一维psize:表示psize个状态, 第二位两个状态:0表示不买股票 1表示买股票
// 第三维 表示0,1,2表示完成交易的次数
int dp[psize][2][3];
dp[0][0][0] = dp[0][0][1] = dp[0][0][2] = 0;
dp[0][1][0] = dp[0][1][1] = dp[0][1][2] = -prices[0];
for(int i=1; i<psize; i++){
dp[i][0][0] = 0; // 不买股票,也不交易
dp[i][0][1] = max(dp[i-1][0][1], dp[i-1][1][0]+prices[i]);
dp[i][0][2] = max(dp[i-1][0][2], dp[i-1][1][1]+prices[i]);
dp[i][1][0] = max(dp[i-1][1][0], dp[i-1][0][0]-prices[i]);
dp[i][1][1] = max(dp[i-1][1][1], dp[i-1][0][1]-prices[i]);
dp[i][1][2] = 0; // 买股票,完成2次交易就不能动了。
}
return max(dp[psize-1][0][2],dp[psize-1][0][1]);
}
二维dp解法:
int maxProfit(vector<int>& prices) {
int psize = prices.size();
if(psize < 2) return 0;
// 第一维psize表示psize天
// 第二维压缩为五个状态:0-没有交易,1-买入一次交易,2-完成一次交易,3-买入第二次交易,4-完成第二次交易
int dp[psize][5];
dp[0][0] = dp[0][2] = dp[0][4] = 0;
dp[0][1] = dp[0][3] = -prices[0];
for(int i=1; i<psize; i++){
dp[i][0] = 0;
dp[i][1] = max(dp[i-1][0] - prices[i], dp[i-1][1]);
dp[i][2] = max(dp[i-1][1] + prices[i], dp[i-1][2]);
dp[i][3] = max(dp[i-1][2] - prices[i], dp[i-1][3]);
dp[i][4] = max(dp[i-1][3] + prices[i], dp[i-1][4]);
}
return max(dp[psize-1][4], dp[psize-1][2]);
}
一维dp解法:
int maxProfit(vector<int>& prices) {
int psize = prices.size();
if(psize < 2) return 0;
// 5个分别表示未交易状态,买入1次,卖出1次,买入2次,卖出2次
vector<int> dp(5,INT_MIN);
dp[0] = 0; dp[1] = -prices[0];
for(int i=1; i<psize; i++){
dp[0] = 0;
dp[1] = max(dp[1],dp[0]-prices[i]);
dp[2] = max(dp[2],dp[1]+prices[i]);
dp[3] = max(dp[3],dp[2]-prices[i]);
dp[4] = max(dp[4],dp[3]+prices[i]);
}
return max(dp[4], dp[2]);
}
其他买卖股票最佳时机
- 买卖股票最佳最佳时机 leetcode 121 暴力解法,不能AC:
int maxProfit(vector<int>& prices) {
int psize = prices.size();
if(psize < 2) return 0;
int min_price = prices[0], ret = 0;
for(int i=0; i<psize-1; i++){
for(int j=i+1; j<psize; j++) if(prices[j] - prices[i] > ret) ret = prices[j] - prices[i];
}
return ret;
}
动态规划:
// dp[i] = max(dp[i-1], prices[i]-min_price); 状态转移方程
int maxProfit(vector<int>& prices) {
int psize = prices.size();
if(psize < 2) return 0;
int min_price = prices[0];
vector<int> dp(psize,0);
for(int i=1; i<psize; i++){
min_price = min(min_price, prices[i]);
dp[i] = max(dp[i-1],prices[i]-min_price);
}
return dp[psize-1];
}
动态规划空间压缩:
// dp[i] = max(dp[i-1], prices[i]-min_price); 状态转移方程
int maxProfit(vector<int>& prices) {
int psize = prices.size();
if(psize < 2) return 0;
int min_price = prices[0], max_price = 0;
vector<int> dp(psize,0);
for(int i=1; i<psize; i++){
max_price = max(max_price,prices[i]-min_price);
min_price = min(min_price, prices[i]);
}
return max_price;
}
- 买卖股票最佳时机II leetcode 122 贪心解法:
int maxProfit(vector<int>& prices) {
int psize = prices.size();
if(psize < 2) return 0;
int ret = 0;
for(int i=1; i<psize; i++){
if(prices[i] > prices[i-1]) ret += (prices[i]-prices[i-1]);
}
return ret;
}
动态规划解法:
int maxProfit(vector<int>& prices) {
int psize = prices.size();
if(psize < 2) return 0;
vector<vector<int>> dp(psize, vector<int>(2,0));
dp[0][1] = -prices[0];
for(int i=1; i<psize; i++){
dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i]); // 不动或者卖股票赚钱
dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i]); // 不动或者花钱买股票
}
return dp[psize-1][0]; // 最终返回最后持有的现金数量
}
动态规划空间压缩解法:
int maxProfit(vector<int>& prices) {
int psize = prices.size();
if(psize < 2) return 0;
int preCash = 0;
int preStock = -prices[0];
for(int i=1; i<psize; i++){
preCash = max(preCash, preStock + prices[i]); // 不动或者卖股票赚钱
preStock = max(preStock, preCash - prices[i]); // 不动或者花钱买股票
}
return preCash; // 最终返回最后持有的现金数量
}