当前位置: 首页 > article >正文

动态规划 之 简单多状态 dp 问题 算法专题

一. 按摩师

按摩师

  1. 状态表示
    根据经验 + 题目要求
    dp[i] 表示: 选择到i位置时, 此时的最长预约时长
    但是根据题目又分成两种情况:
    f[i] : 选择到 i 位置的时候, nums[i] 必选, 此时的最长预约时长
    g[i] : 选择到 i 位置的时候, nums[i] 不选, 此时的最长预约时长
  2. 状态转移方程
    f[i] 如果i 位置必选, 那么f[i - 1] 位置必不选, 就等于g[i - 1] + nums[i]
    g[i] 如果i位置不选, 那么g[i - 1] 位置有两种情况, 如果[i - 1] 选, 那么就=f[i - 1] ,如果[i - 1] 不选, 那么就=g[i - 1] , 但是我们要的是最长时长, 所以
  • f[i] = g[i - 1] + nums[i]
  • g[i] = max(f[i - 1], g[i - 1])
  1. 初始化
    f[0] = nums[0] g[0] = 0;
  2. 填表顺序
    从左往右 两个表一起填
  3. 返回值
    返回max(f[n], g[n])
class Solution {
    public int massage(int[] nums) {
        //1. 建表
        //2. 初始化
        //3. 填表
        //4. 返回值
        int n = nums.length;
        if(n == 0) return 0;
        int[] f = new int[n];
        int[] g = new int[n];
        f[0] = nums[0];
        
        for(int i = 1; i < n; i++){
            f[i] = g[i - 1] + nums[i];
            g[i] = Math.max(f[i - 1], g[i - 1]);
        }

        return Math.max(f[n - 1], g[n - 1]);
    }
}

二. 打家劫舍II

打家劫舍II
分析: 通过分类讨论, 可以将环形的问题转化为线性问题
1.如果第0家偷, 那么第一家和最后一家就不能偷, 所以第二家和倒数第二家之间就可以按照正常的情况考虑
2.如果第0家不偷, 那么剩下的都可以正常进行了

  1. 状态表示
    根据经验 + 题目要求
    dp[i] 表示: 选择到i位置时, 此时偷的最大金额
    但是根据题目又分成两种情况:
    f[i] : 选择到 i 位置的时候, nums[i] 必选, 此时偷的最大金额
    g[i] : 选择到 i 位置的时候, nums[i] 不选, 此时偷的最大金额
  2. 状态转移方程
    f[i] 如果i 位置必选, 那么f[i - 1] 位置必不选, 就等于g[i - 1] + nums[i]
    g[i] 如果i位置不选, 那么g[i - 1] 位置有两种情况, 如果[i - 1] 选, 那么就=f[i - 1] ,如果[i - 1] 不选, 那么就=g[i - 1] , 但是我们要的是偷的最大金额, 所以
  • f[i] = g[i - 1] + nums[i]
  • g[i] = max(f[i - 1], g[i - 1])
  1. 初始化
    f[0] = nums[0] g[0] = 0;
  2. 填表顺序
    从左往右 两个表一起填
  3. 返回值
    返回第0家偷和不偷的最大值
class Solution {
    int[] f;
    int[] g;
    int[] nums;

    public int rob(int[] _nums) {
        // 1. 建表
        // 2. 初始化
        // 3. 填表
        // 4. 返回值
        nums = _nums;
        int n = _nums.length;
        int x = rob1(2, n - 2) + nums[0];
        int y = rob1(1, n - 1);

        return Math.max(x, y);
    }

    public int rob1(int left, int right) {
        if(left > right) return 0;
        int n = nums.length;
        f = new int[n];
        g = new int[n];
        f[left] = nums[left];
        for (int i = left; i <= right; i++) {
            f[i] = g[i - 1] + nums[i];
            g[i] = Math.max(g[i - 1], f[i - 1]);
        }

        return Math.max(f[right], g[right]);
    }

}

三. 删除并获得点数

删除并获得点数
分析: 由于是删除大于或和小于的数, 也就是说相邻的数是不能再选的, 那么和打家劫舍问题是类似的
但是这些数并不是相邻的并且无序, 所以我们需要重新定义一个数组arr来存储
点数代表下标, 对应的值就是该点数的总和

  1. 状态表示
    根据经验 + 题目要求
    dp[i] 表示: 选择到i位置时, 此时获得的最大点数
    但是根据题目又分成两种情况:
    f[i] : 选择到 i 位置的时候, nums[i] 必选, 此时获得的最大点数
    g[i] : 选择到 i 位置的时候, nums[i] 不选, 此时获得的最大点数
  2. 状态转移方程
    f[i] 如果i 位置必选, 那么f[i - 1] 位置必不选, 就等于g[i - 1] + arr[i]
    g[i] 如果i位置不选, 那么g[i - 1] 位置有两种情况, 如果[i - 1] 选, 那么就=f[i - 1] ,如果[i - 1] 不选, 那么就=g[i - 1] , 但是我们要的是获得的最大点数, 所以
  • f[i] = g[i - 1] + nums[i]
  • g[i] = max(f[i - 1], g[i - 1])
  1. 初始化
    f[0] = arr[0] g[0] = 0;
  2. 填表顺序
    从左往右 两个表一起填
  3. 返回值
    返回max(f[n - 1], g[n - 1])
class Solution {
    public int deleteAndEarn(int[] nums) {
        //预处理
        int n = 10001;
        int[] arr = new int[n];
        for(int x: nums) arr[x] += x;

        //建表
        //初始化
        //填表
        //返回值
        
        int[] f = new int[n];
        int[] g = new int[n];

        f[0] = arr[0];
        
        for(int i = 1; i < n; i++){
            f[i] = g[i - 1] + arr[i];
            g[i] = Math.max(f[i - 1], g[i - 1]);
        }

        return Math.max(f[n - 1], g[n - 1]);
    }
}

四, 粉刷房子

粉刷房子

  1. 状态表示
    根据经验 + 题目要求
    dp[i] 表示: 选择到i位置时, 此时粉刷的最小花费
    但是根据题目又分成两种情况:
    如果第i个房子, 粉刷的是红色的, 那么前一个房子只能是蓝色或绿色, 同理其他情况
    所以dp可以弄成二维数组, dp[i][j] 表示最小费用, j表示颜色 0红色,1蓝色,2绿色
  2. 状态转移方程
    1.如果i选择红色, dp[i][0], 那么dp[i - 1]只能选择[1][2], 要的是最小花费, 所以
  • dp[i][0] = Math.min(dp[i - 1][1], dp[i - 1][2]) + costs[i - 1][0]

2.如果i选择蓝色, dp[i][0], 那么dp[i - 1]只能选择[1][2], 要的是最小花费, 所以

  • dp[i][1] = Math.min(dp[i - 1][0], dp[i - 1][2]) + costs[i - 1][1]

3.如果i选择红色, dp[i][0], 那么dp[i - 1]只能选择[1][2], 要的是最小花费, 所以

  • dp[i][2] = Math.min(dp[i - 1][0], dp[i - 1][1]) + costs[i - 1][2]
  1. 初始化
    本道题初始化较为复杂, 所以使用虚拟节点来帮助我们初始化, 要注意两个注意事项: 初始化与对应关系
    最开始是0的位置最小花费应该初始化为0, 因为还没有花费
  2. 填表顺序
    从左往右 两个表一起填
  3. 返回值
    返回min(dp[n][0], dp[n][1], dp[n][2])
class Solution {
    public int minCost(int[][] costs) {
        int n = costs.length;
        
        int[][] dp = new int[n + 1][3];
        
        for(int i = 1; i <= n; i++){//i遍历的是行
                dp[i][0] = Math.min(dp[i - 1][1], dp[i - 1][2]) + costs[i - 1][0];
                dp[i][1] = Math.min(dp[i - 1][0], dp[i - 1][2]) + costs[i - 1][1];
                dp[i][2] = Math.min(dp[i - 1][0], dp[i - 1][1]) + costs[i - 1][2];
        }
        return Math.min(Math.min(dp[n][0], dp[n][1]), dp[n][2]);
    }
}

五. 买卖股票的最佳时机含冷冻期

买卖股票的最佳时机含冷冻期

  1. 状态表示
    根据经验 + 题目要求
    dp[i] 表示: 选择到第i天位置时, 此时的最大利润
    但是到第i天又分成三种情况:
    可能处于"买入"状态, "可交易"状态, "冷冻期"状态
    所以用二维数组[i][j]状态表示 0"买入"状态, 1"可交易"状态, 2"冷冻期"状态
  2. 状态转移方程
    1.如果处于"买入"状态, 那么第i - 1天, 可能处于"买入"状态(i - 1天买完后第i天没卖, 等于啥也没干), 可能处于"可交易"状态(i - 1天不是冷冻期, 可以进行购买), 此时是需要-价格, 进行购买, 由于要最大利润, 对两次状态的结果取最大值
  • dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i])

2.如果处于"可交易"状态, 那么第i - 1天, 可能处于"可交易"状态(i - 1天之前就卖了, 第i天还没买, 等于啥也没干), 可能处于"冷冻期"状态(i - 1天卖了, 第i天还没买, 等于啥也没干), 由于要最大利润, 对两次状态的结果取最大值

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

3.如果处于"冷冻期"状态, 那么第i - 1天, 可能处于"买入"状态(i - 1天买了, 第i天卖了), , 此时是需要+价格, 进行购买,

  • dp[i][2] =dp[i - 1][0] + price[i]
  1. 初始化
    只需对0位置进行初始化
    dp[0][0] 说明买了股票, 利润为-prices[0]
    dp[0][1] 说明可以买, 利润为0
    dp[0][2] 说明冷冻期, 利润为0
  2. 填表顺序
    从左往右 三个表一起填
  3. 返回值
    返回max(dp[n - 1][0], dp[n - 1][1], dp[n - 1][2])
class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        int[][] dp = new int[n][3];
        dp[0][0] = -prices[0];

        for(int i = 1; i < n; i++){
            dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
            dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][2]);
            dp[i][2] =dp[i - 1][0] + prices[i];
        }
        return Math.max(Math.max(dp[n - 1][0], dp[n - 1][1]), dp[n - 1][2]);
    }
}

六. 买卖股票的最佳时机含手续费

买卖股票的最佳时机含手续费

  1. 状态表示
    根据经验 + 题目要求
    dp[i] 表示: 选择到第i天位置时, 此时的最大利润
  2. 状态转移方程
    第i天位置时, 有两种情况
    1.可能处于"买入"状态
    那么i - 1 位置可能是"买入"状态(啥也不干), 可能是"可交易"状态(买股票)
    f[i] = max(f[i - 1], g[i - 1] - p[i])
    2.可能处于"可交易"状态
    那么i - 1 位置可能是"买入"状态(卖股票, 再加上手续费), 可能是"可交易"状态(啥也不干)
    g[i] = max(f[i - 1] + p[i] - fee, g[i - 1])
  3. 初始化
    f[0] = -p[0] g[0] = 0
  4. 填表顺序
    从左往右 两个表一起填
  5. 返回值
    max(g[n - 1], f[n - 1])
class Solution {
    public int maxProfit(int[] prices, int fee) {
        int n = prices.length;
        int[] f = new int[n];
        int[] g = new int[n];
        f[0] = -prices[0];

        for(int i = 1; i < n; i++){
            f[i] = Math.max(f[i - 1], g[i - 1] - prices[i]);
            g[i] = Math.max(f[i - 1] + prices[i] - fee, g[i - 1]);
        }

        return Math.max(g[n - 1], f[n - 1]);
    }
}

七. 买卖股票的最佳时机III

买卖股票的最佳时机III

  1. 状态表示
    根据经验 + 题目要求
    dp[i] 表示: 选择到第i天结束之后, 此时的最大利润
    第i天位置时, 有两种情况, "买入"状态 和 "可交易"状态, 但是还要记录交易次数, 所以我们要使用二维数组
  2. 状态转移方程
    1.第i天可能处于"买入"状态, 此时完成了j次交易, i为最大利润
    那么i - 1 位置可能是"买入"状态(啥也不干), 可能是"可交易"状态(买股票)
    f[i][j] = max(f[i - 1][j], g[i - 1][j] - p[i])
    2.第i天可能处于"可交易"状态, 此时完成了j次交易, i为最大利润
    那么i - 1 位置可能是"买入"状态(卖股票, 交易次数 + 1), 可能是"可交易"状态(啥也不干)
    g[i][j] = max(f[i - 1][j - 1] + p[i], g[i - 1][j])
  3. 初始化
    f需要初始化第一行, g需要初始化第一行第一列
    所以我们可以修改一下状态转移方程, 只初始化第一行即可
    g[i][j] = g[i - 1][j];
    if(j - 1 >= 0) max(f[i - 1][j - 1] + p[i], g[i][j])

f[0][0] = -p[0] 第0天不可能完成多笔交易, 所以第一行其余填-0x3f3f3f3f (INT_MIN的一半, 不初始化为INT_MIN, 防止溢出
g[0][0] = 0 第一行其余填-0x3f3f3f3f
4. 填表顺序
从左往右 从上往下 两个表一起填
5. 返回值
返回可交易表最后一行的最大值

class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        int INF = 0x3f3f3f3f;
        int[][] f = new int[n][3];
        int[][] g = new int[n][3];
        for(int j = 0; j < 3; j++){
            f[0][j] = g[0][j] = -INF;
        }
        f[0][0] = -prices[0];
        g[0][0] = 0;

        for(int i = 1; i < n; i++){
            for(int j = 0; j < 3; j++){
                f[i][j] = Math.max(f[i - 1][j], g[i - 1][j] - prices[i]);
                g[i][j] = g[i - 1][j];
                if(j - 1 >= 0) g[i][j] = Math.max(g[i][j], f[i - 1][j - 1] + prices[i]);
            }
        }

        int ret = 0;
        for(int j = 0; j < 3; j++){
            ret = Math.max(ret, g[n - 1][j]);
        }
        return ret;
    }
}

八. 买卖股票的最佳时机IV

买卖股票的最佳时机IV

  1. 状态表示
    根据经验 + 题目要求
    dp[i] 表示: 选择到第i天结束之后, 此时的最大利润
    第i天位置时, 有两种情况, "买入"状态 和 "可交易"状态, 但是还要记录交易次数, 所以我们要使用二维数组
  2. 状态转移方程
    1.第i天可能处于"买入"状态, 此时完成了j次交易, i为最大利润
    那么i - 1 位置可能是"买入"状态(啥也不干), 可能是"可交易"状态(买股票)
    f[i][j] = max(f[i - 1][j], g[i - 1][j] - p[i])
    2.第i天可能处于"可交易"状态, 此时完成了j次交易, i为最大利润
    那么i - 1 位置可能是"买入"状态(卖股票, 交易次数 + 1), 可能是"可交易"状态(啥也不干)
    g[i][j] = max(f[i - 1][j - 1] + p[i], g[i - 1][j])
  3. 初始化
    f需要初始化第一行, g需要初始化第一行第一列
    所以我们可以修改一下状态转移方程, 只初始化第一行即可
    g[i][j] = g[i - 1][j];
    if(j - 1 >= 0) max(f[i - 1][j - 1] + p[i], g[i][j])

f[0][0] = -p[0] 第0天不可能完成多笔交易, 所以第一行其余填-0x3f3f3f3f (INT_MIN的一半, 不初始化为INT_MIN, 防止溢出
g[0][0] = 0 第一行其余填-0x3f3f3f3f
4. 填表顺序
从左往右 从上往下 两个表一起填
5. 返回值
返回可交易表最后一行的最大值

class Solution {
    public int maxProfit(int k, int[] prices) {
        int n = prices.length;
        k = Math.min(k, n / 2);
        int INF = 0x3f3f3f3f;
        int[][] f = new int[n][k + 1];
        int[][] g = new int[n][k + 1];
        for(int j = 0; j <= k; j++){
            f[0][j] = g[0][j] = -INF;
        }
        f[0][0] = -prices[0];
        g[0][0] = 0;

        for(int i = 1; i < n; i++){
            for(int j = 0; j <= k; j++){
                f[i][j] = Math.max(f[i - 1][j], g[i - 1][j] - prices[i]);
                g[i][j] = g[i - 1][j];
                if(j - 1 >= 0) g[i][j] = Math.max(g[i][j], f[i - 1][j - 1] + prices[i]);
            }
        }
        int ret = 0;
        for(int j = 0; j <= k; j++){
            ret = Math.max(ret, g[n - 1][j]);
        }
        return ret;
    }
}

http://www.kler.cn/a/390504.html

相关文章:

  • 第74期 | GPTSecurity周报
  • SpringSecurity源码中核心类
  • 【Android、IOS、Flutter、鸿蒙、ReactNative 】约束布局
  • 基于表格滚动截屏(表格全部展开,没有滚动条)
  • Spring Boot 核心配置文件
  • Redis - String 字符串
  • Vue — 组件化开发
  • ZYX地图瓦片转mbtiles文件(Python)
  • Postman上传图片如何处理
  • Docker-软件容器平台
  • springboot基于java无人超市管理系统,计算机毕业设计项目源码314,计算机毕设程序(LW+开题报告、中期报告、任务书等全套方案)
  • 漫谈MCU优化:从硬件设计优化到可靠性挑战
  • NVM切换本地node版本
  • Vue前端开发:gsap动画库
  • 10.桥接模式设计思想
  • 基础网络安全知识
  • 修改msyql用户密码及更新mysql密码策略
  • Redis - Hash 哈希
  • MR30分布式IO热插拔:智能时代的便捷与高效
  • uni-app小程序echarts中tooltip被遮盖
  • ★ 算法OJ题 ★ 前缀和算法(下)
  • [OS] 区分按位与()和逻辑与()
  • C# 如何将winform只生成一个绿色文件?
  • 02-1_MVCC版本链清理
  • 手写一些方法
  • Mac保护电池健康,延长电池使用寿命的好方法