动态规划系列 | 状态机模型(上)| 练完这些就算入门了!

状态机模型

    • 核心思想
    • LeetCode-198. 打家劫舍
      • 题目描述
      • 问题分析
      • 状态压缩
      • 复杂度分析
      • 程序代码
    • LeetCode-188. 买卖股票的最佳时机 Ⅳ
      • 题目描述
      • 问题分析
      • 状态压缩
      • 复杂度分析
      • 程序代码
    • LeetCode-309. 买卖股票的最佳时机含冷冻期
      • 题目描述
      • 问题分析
      • 状态压缩
      • 复杂度分析
      • 程序代码

核心思想

用状态机模型求解动态规划问题,就是将原始问题用状态机模型进行表示,即每个节点表示状态,每条边表示一个状态转移,边上的权值表示转移的代价或收益

状态机模型的目标是找到一条从初始状态出发,经过若干次状态转移,达到某个终止状态的路径,使得最终的结果值最大或最小

LeetCode-198. 打家劫舍

题目描述

原题链接

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

问题分析

状态定义

  • dp[i][0]:表示不偷窃第 i 家,前 i 家的最大收益。
  • dp[i][1]:表示偷窃第 i 家,前 i 家的最大收益。

状态转移

  • 若不偷窃第 i 家,则可以从dp[i-1][0]dp[i-1][0]转移过来,即dp[i][0] = max(dp[i-1][0], dp[i-1][1])
  • 若偷窃第 i 家,因为不能偷窃两间相邻的房屋,因此dp[i][1]只能从第 i-1 家没有被偷窃的状态转移过来,即dp[i][1] = dp[i-1][0] + nums[i]

对应的状态转移图如下:

动态规划系列 | 状态机模型(上)| 练完这些就算入门了!_第1张图片

状态压缩

观察上述状态压缩图,我们可以进一步将状态压缩到只有两个状态:

动态规划系列 | 状态机模型(上)| 练完这些就算入门了!_第2张图片

压缩后的状态转移方程:

  • dp[0] = max(dp[0], dp[1])
  • dp[1] = dp[0] + nums[i]

复杂度分析

时间复杂度为: O ( n ) O(n) O(n)

空间复杂度为: O ( 1 ) O(1) O(1)

程序代码

class Solution {
public:
    int rob(vector<int>& nums) {
        vector<int> dp(2, 0);
        dp[1] = nums[0];
        for(int i = 1; i < nums.size(); i++) {
            int t = dp[0];
            dp[0] = max(dp[0], dp[1]);
            dp[1] = t + nums[i];
        }
        return max(dp[0], dp[1]);
    }
};

LeetCode-188. 买卖股票的最佳时机 Ⅳ

题目描述

原题链接

给你一个整数数组 prices 和一个整数 k ,其中 prices[i] 是某支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。也就是说,你最多可以买 k 次,卖 k 次。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

问题分析

我们将股票买入记为一次新的交易的开始。

状态定义

  • dp[i][j][0]:第 i 天,正在进行第 j 次交易,手中没有持有股票
  • dp[i][j][1]:第 i 天,正在进行第 j 次交易,手中持有股票

状态转移

  • 若第 i 天,正在进行第 j 次交易,手中没有持有股票,则上一天可能正在进行第 j 次交易,且手中没有持有股票。或者上一天持有股票,并在第 i 天卖出,即dp[i][j][0] = max(dp[i-1][j][0], dp[i-1][j][1] + prices[i])
  • 若第 i 天,正在进行第 j 次交易,手中持有股票,则上一天可能正在进行第 j 次交易,且手中没有持有股票。或者上一天不持有股票,并在第 i 天买入,即dp[i][j][1] = max(dp[i-1][j][1], dp[i-1][j-1][0] - prices[i])

状态转移图

动态规划系列 | 状态机模型(上)| 练完这些就算入门了!_第3张图片

状态压缩

上述的状态转化图可以进一步压缩:

动态规划系列 | 状态机模型(上)| 练完这些就算入门了!_第4张图片

压缩后的状态转移方程:

  • dp[j][0] = max(dp[j][0], dp[j][1] + prices[i])
  • dp[j][1] = max(dp[j][1], dp[j-1][0] - prices[i])

复杂度分析

时间复杂度: O ( n × k ) O(n×k) O(n×k)

空间复杂度: O ( 2 × k ) O(2×k) O(2×k)

程序代码

class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {
        vector<vector<int>> dp(k+1, vector<int>(2, 0));
        
        for(int i = 1; i <= k; i++)  dp[i][1] = -prices[0];

        for(int i = 1; i < prices.size(); i++) {
            for(int j = k; j >= 1; j--) {
                dp[j][0] = max(dp[j][0], dp[j][1] + prices[i]);
                dp[j][1] = max(dp[j][1], dp[j-1][0] - prices[i]);
            }
        }

        return dp[k][0];
    }
};

LeetCode-309. 买卖股票的最佳时机含冷冻期

题目描述

给定一个整数数组prices,其中第 prices[i] 表示第 _i_ 天的股票价格 。​

设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):

  • 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

问题分析

我们将股票买入记为一次新的交易的开始。

状态定义

  • dp[i][0]:第 i 天,手中没有持有股票第 1 天。
  • dp[i][1]:第 i 天,手中没有持有股票超天数大于等于 2。
  • dp[i][2]:第 i 天,手中持有股票。

状态转移

  • dp[i][0] = dp[i-1][2] + prices[i]:第 i 天,手中没有持有股票第一天,一定是第 i -1 天持有股票,并在第 i 天卖出。
  • dp[i][1] = max(dp[i-1][1], dp[i-1][0]):第 i 天,手中没有持有股票天数大于等于 2。则上一状态可能是不持有股票天数大于等于 2(dp[i-1][1])或第 i -1 天是不持有股票第一天(dp[i-1][0])。
  • dp[i][2] = max(dp[i-1][2], dp[i-1][1] - prices[i]):第 i 天,正在进行第 j 次交易,手中持有股票。则上一状态可能是持有股票(dp[i-1][2])或第 i-1 天不持有股票天数大于等于2,并在第 i 天买入股票,即dp[i-1][1] - prices[i]

状态转移图

动态规划系列 | 状态机模型(上)| 练完这些就算入门了!_第5张图片

状态压缩

上述状态机模型图可以进一步压缩:

动态规划系列 | 状态机模型(上)| 练完这些就算入门了!_第6张图片

压缩后的状态转移方程:

  • dp[0] = dp[2] + prices[i]
  • dp[1] = max(dp[0], dp[1])
  • dp[2] = max(dp[1] - prices[i], dp[2])

复杂度分析

时间复杂度: O ( n ) O(n) O(n)

空间复杂度: O ( 1 ) O(1) O(1)

程序代码

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        vector<int> dp(3, 0);

        dp[2] = -prices[0];

        for(int i = 1; i < n; i++) {
            int t0 = dp[0], t1 = dp[1], t2 = dp[2];
            dp[0] = t2 + prices[i];
            dp[1] = max(t0, t1);
            dp[2] = max(t1 - prices[i], t2);
        }

        return max(dp[0], dp[1]);
    }
};

你可能感兴趣的:(手撕算法,动态规划,算法,leetcode)