算法导论(动态规划)——简单多状态

news2025/4/25 21:57:32

算法思路(17.16)

  1. 状态表示
    在处理线性动态规划问题时,我们可以通过“经验 + 题目要求”来定义状态表示。通常有两种选择:

    • 以某个位置为结尾的情况;
    • 以某个位置为起点的情况。

    本题中,我们选择更常用的方式,以某个位置为结尾,并结合题目要求,定义状态表示如下:

    dp[i] 表示选择到位置 i 时的最长预约时长。

    但是在处理位置 ii 时,我们会面临“选择”或“不选择”的两种决策,因此需要将状态细分为:

    • f[i] 表示选择到 i 位置时,nums[i] 必选,此时的最长预约时长。f[i] 表示选择到 i 位置时,nums[i] 必选,此时的最长预约时长。
    • g[i] 表示选择到 i 位置时,nums[i] 不选,此时的最长预约时长。g[i] 表示选择到 i 位置时,nums[i] 不选,此时的最长预约时长。
  2. 状态转移方程
    由于状态表示涉及两个部分,我们分别分析这两个状态的转移关系:

    • 对于 f[i]:

      • 如果选择 nums[i] 必选,我们只需考虑位置 i−1 在不选择时的最长预约时长,因此:
      f[i]=g[i−1]+nums[i]
    • 对于 g[i]:

      • 如果 nums[i] 不选,则 i−1 位置可以选择或不选择,因此:
      g[i]=max⁡(f[i−1],g[i−1])
  3. 初始化
    本题的初始化相对简单,无需添加辅助节点。只需进行以下初始化即可:

    f[0]=nums[0],g[0]=0
  4. 填表顺序
    根据状态转移方程,填表顺序为“从左往右”,同时填充两个状态表。

  5. 返回值
    根据状态表示,最终返回的结果为:

    max⁡(f[n−1],g[n−1])

    这代表在处理到最后一个位置时的最长预约时长。

C++:

class Solution {
public:
 int massage(vector<int>& nums) {
 // 1. 创建⼀个 dp 表 
 // 2. 初始化 
 // 3. 填表 
 // 4. 返回值 
 int n = nums.size();
 if(n == 0) return 0; // 处理边界条件 
 vector<int> f(n);
 auto g = f;
 f[0] = nums[0];
 for(int i = 1; i < n; i++)
 {
 f[i] = g[i - 1] + nums[i];
 g[i] = max(f[i - 1], g[i - 1]);
 }
 return max(f[n - 1], g[n - 1]);
 }
};

Java:

class Solution 
{
 public int massage(int[] nums) 
 {
 // 1. 创建 dp 表 
 // 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(g[n - 1], f[n - 1]);
 }
}

算法思路(213)

本问题是“按摩师”问题的变种,具体而言,它涉及一个“环形”模式,而不是单排模式。由于首尾房屋相连,这使得问题的处理方式有所不同。但我们可以将“环形”问题转化为两个单排问题的求解:

  1. 偷第一个房屋:在这种情况下,最高金额为 x。此时由于偷了第一个房屋,不能再偷最后一个房屋,因此只需在区间 [0,n−2] 内进行偷窃。

  2. 不偷第一个房屋:在这种情况下,最高金额为 y。此时可以偷最后一个房屋,因此只需在区间 [1,n−1] 内进行偷窃。

  3. 最终结果:通过计算上述两种情况的最大值,我们可以得到环形问题的最终结果。也就是说,问题已有效转化为求解两个单排问题的最大值:

    结果=max⁡(x,y)

C++:

class Solution 
{
public:
 int rob(vector<int>& nums){
 int n = nums.size();
 // 两种情况下的最⼤值 
 return max(nums[0] + rob1(nums, 2, n - 2), rob1(nums, 1, n - 1));
 }
 int rob1(vector<int>& nums, int left, int right){
 if(left > right) return 0;
 // 1. 创建 dp 表 
 // 2. 初始化 
 // 3. 填表 
 // 4. 返回结果 
 int n = nums.size();
 vector<int> f(n);
 auto g = f;
 f[left] = nums[left]; // 初始化 
 for(int i = left + 1; i <= right; i++)
 {
 f[i] = g[i - 1] + nums[i];
 g[i] = max(f[i - 1], g[i - 1]);
 }
 return max(f[right], g[right]);
 }
};

Java:

class Solution 
{
 public int rob(int[] nums) 
 {
 int n = nums.length;
 return Math.max(nums[0] + rob1(nums, 2, n - 2), rob1(nums, 1, n - 1));
 }
 public int rob1(int[] nums, int left, int right)
 {
 if(left > right) return 0;
 // 1. 创建 dp 表 
 // 2. 初始化 
 // 3. 填表 
 // 4. 返回 
 int n = nums.length;
 int[] f= new int[n];
 int[] g= new int[n];
 f[left] = nums[left];
 for(int i = left + 1; 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]);
 }
}

算法思路(740)

本题实质上是“按摩师”问题的变种。注意到题目描述,当选择某个数字 x 时,x−1 和 x+1 的数值不能被选择。这与“按摩师”问题中的逻辑相似:选择位置 i 的金额后,不能选择位置 i−1 和 i+1 的金额。

因此,我们可以通过以下步骤解决这个问题:

  1. 创建哈希数组

    • 根据题目中的数据范围,创建一个大小为 10001 的哈希数组 hash。
    • 遍历数组 nums,将数组中每个元素 x 的值累加到哈希数组的对应位置 hash[x] 上。有了这个哈希数组,我们能将相同金额的数值进行聚合。
  2. 应用按摩师策略

C++:

class Solution {
public:
 int deleteAndEarn(vector<int>& nums) {
 const int N = 10001;
 // 1. 预处理 
 int arr[N] = { 0 };
 for(auto x : nums) arr[x] += x;
 // 2. 在 arr 数组上,做⼀次 “打家劫舍” 问题 
 // 创建 dp 表 
 vector<int> f(N);
 auto g = f;
 // 填表 
 for(int i = 1; i < N; i++)
 {
 f[i] = g[i - 1] + arr[i];
 g[i] = max(f[i - 1], g[i - 1]);
 }
 // 返回结果 
 return max(f[N - 1], g[N - 1]);
 }
};

Java:

class Solution 
{
 public int deleteAndEarn(int[] nums) 
 {
 int n = 10001;
 // 1. 预处理 
 int[] arr = new int[n];
 for(int x : nums) arr[x] += x;
 // 2. dp
 // 创建 dp 表 
 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]);
 }
}

算法思路(091)

  1. 状态表示
    在处理线性动态规划问题时,我们可以通过“经验 + 题目要求”来定义状态表。通常有两种选择:

    • 以某个位置为结尾;
    • 以某个位置为起点。

    本题中,我们选择以某个位置为结尾的方法,并结合题目要求,定义状态表示为:

    • dp[i][0]:表示到达位置 ii 时,如果最后一个位置刷上“红色”,所需的最小花费;
    • dp[i][1]:表示到达位置 ii 时,如果最后一个位置刷上“蓝色”,所需的最小花费;
    • dp[i][2]:表示到达位置 ii 时,如果最后一个位置刷上“绿色”,所需的最小花费。
  2. 状态转移方程
    由于状态表示定义了三个状态,我们需要针对每个状态推导其转移方程:

    • 对于 dp[i][0](最后一个位置刷红色):

      dp[i][0]=min⁡(dp[i−1][1],dp[i−1][2])+costs[i−1][0]

      这里,我们需要获取位置 i−1i−1 刷蓝色或绿色情况下的最小花费,然后加上当前位置的红色刷费。

    • 对于 dp[i][1](最后一个位置刷蓝色):

      dp[i][1]=min⁡(dp[i−1][0],dp[i−1][2])+costs[i−1][1]
    • 对于 dp[i][2](最后一个位置刷绿色):

      dp[i][2]=min⁡(dp[i−1][0],dp[i−1][1])+costs[i−1][2]
  3. 初始化
    设定一个辅助节点来帮助我们初始化工作。当我们添加一个辅助节点时,需确保两个关键点:

    • 辅助节点内的值:应保证能正确初始化后续状态表;
    • 下标映射关系:在本题中,我们添加的辅助节点默认设置为 0。
  4. 填表顺序
    根据状态转移方程,应按顺序“从左往右”填充这三个状态表。

  5. 返回值
    最终,我们需要返回在所有颜色情况下的最小花费:

    min⁡(dp[n][0],dp[n][1],dp[n][2])

    这里 nn 表示房屋的数量,确保能够在最后一个位置中选择刷上任一颜色的最小花费。

C++:

class Solution {
public:
 int minCost(vector<vector<int>>& costs) {
 // dp[i][j] 第i个房⼦刷成第j种颜⾊最⼩花费 
 int n = costs.size();
 vector<vector<int>> dp(n + 1, vector<int>(3));
 for (int i = 1; i <= n; i++) {
 dp[i][0] = min(dp[i - 1][1], dp[i - 1][2]) + costs[i - 1][0];
 dp[i][1] = min(dp[i - 1][0], dp[i - 1][2]) + costs[i - 1][1];
 dp[i][2] = min(dp[i - 1][1], dp[i - 1][0]) + costs[i - 1][2];
 }
 return min(dp[n][0], min(dp[n][1], dp[n][2]));
 }
};

Java:

class Solution 
{
 public int minCost(int[][] costs) 
 {
 // 1. 创建 dp 表 
 // 2. 初始化 
 // 3. 填表 
 // 4. 返回结果 
 int n = costs.length;
 int[][] dp = new int[n + 1][3];
 for(int i = 1; i <= n; 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(dp[n][0], Math.min(dp[n][1], dp[n][2]));
 }
}

算法思路(309)

  1. 状态表示
    在处理线性动态规划问题时,我们可以通过“经验 + 题目要求”来定义状态表。针对本问题,有三种状态:

    • 买入状态;
    • 可交易状态;
    • 冷冻期状态。

    因此,我们可以定义一个三维状态数组 dpdp:

    • dp[i][0]:表示在第 ii 天结束后处于“买入”状态时的最大利润;
    • dp[i][1]:表示在第 ii 天结束后处于“可交易”状态时的最大利润;
    • dp[i][2]:表示在第 ii 天结束后处于“冷冻期”状态时的最大利润。
  2. 状态转移方程
    我们需要根据规则推导状态转移方程:

    • 对于 dp[i][0](买入状态):

      • 可以由之前持有股票(保持不变)和今天购入股票(从可交易状态转变)得到:
      dp[i][0]=max⁡(dp[i−1][0],dp[i−1][1]−prices[i])

      其中,dp[i−1][1]−prices[i] 表示在可交易状态下买入股票的利润计算。

    • 对于 dp[i][1] (可交易状态):

      • 由之前在冷冻期不执行任何交易(停滞)和之前处于可交易状态不执行任何交易得到:
      dp[i][1]=max⁡(dp[i−1][1],dp[i−1][2])
    • 对于 dp[i][2] (冷冻期状态):

      • 由之前的持有状态卖出股票进入冷冻期得到:
      dp[i][2]=dp[i−1][0]+prices[i]
  3. 初始化
    第一行的状态初始化较为关键,因为后续状态均依赖于第一行值:

    • dp[0][0]:为了处于买入状态,必须在第一天买入股票,故:
    dp[0][0]=−prices[0]
    • dp[0][1]:什么也不做的情况下,利润为0:
    dp[0][1]=0
    • dp[0][2]:进入冷冻期时手中没有股票,利润为0:
    dp[0][2]=0
  4. 填表顺序
    根据状态转移方程,依次从第一天填充到最后一天,三个状态表需要一起更新,方向为“从左到右”。

  5. 返回值
    最后,我们需要返回在可交易状态或冷冻期状态下的最大利润值:

    max⁡(dp[n−1][1],dp[n−1][2])

C++:

class Solution 
{
public:
 int maxProfit(vector<int>& prices) 
 {
 // 1. 创建 dp 表 
 // 2. 初始化 
 // 3. 填表 
 // 4. 返回结果 
 int n = prices.size();
 vector<vector<int>> dp(n, vector<int>(3));
 dp[0][0] = -prices[0];
 for(int i = 1; i < n; 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][2]);
 dp[i][2] = dp[i - 1][0] + prices[i];
 }
 return max(dp[n - 1][1], dp[n - 1][2]);
 }
};

Java:

class Solution 
{
 public int maxProfit(int[] prices) 
 {
 // 1. 创建 dp 表 
 // 2. 初始化 
 // 3. 填表 
 // 4. 返回值 
 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(dp[n - 1][1], dp[n - 1][2]);
 }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2325775.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LeetCode 438. 找到字符串中所有字母的异位词

438. 找到字符串中所有字母的异位词 题目描述 给定两个字符串 s 和 p&#xff0c;找到 s 中所有 p 的 异位词 的子串&#xff0c;返回这些子串的起始索引。不考虑答案输出的顺序。 输入输出示例及数据范围 思路 这道题的思路其实很简单&#xff0c;就是一个滑动窗口的裸题&a…

java详细笔记总结持续完善

一.Java开发环境的搭建 1. 单位换算 1TB 1024GB 1GB 1024MB 1MB 1024KB 1KB 1024Byte (字节) 1Byte 8 bit(位) 注意&#xff1a;一个字节占8位 2. DOS命令 DOS : Disk Operation System 磁盘操作系统 即用于操作本地磁盘的系统 命令操作符号盘符切换命令盘符名:查看当前文…

wsl2的centos7安装jdk17、maven

JDK安装 查询系统中的jdk rpm -qa | grep java按照查询的结果&#xff0c;删除对应版本 yum -y remove java-1.7.0-openjdk*检查是否删除 java -version 下载JDK17 JDK17&#xff0c;下载之后存到wsl目录下&#xff08;看你自己&#xff09;然后一键安装 sudo rpm -ivh jd…

乐鑫ESP-Mesh-Lite方案,启明云端乐鑫代理商,创新组网拓展智能应用边界

在当今智能化浪潮的背景下&#xff0c;智能家居、智能农业、能源管理等领域对设备组网的需求日益增长。然而&#xff0c;传统的Wi-Fi组网方式常常受限于设备数量、路由器位置以及网络覆盖范围等因素&#xff0c;难以满足复杂场景下的多样化需求。 一方面&#xff0c;需要支持更…

ISIS【路由协议讲解】-通俗易懂!

IS-IS的背景 IS-IS最初是国际标准化组织ISO为它的无连接网络协议CLNP&#xff08;ConnectionLess Network Protocol&#xff09;设计的一种动态路由协议。随着TCP/IP协议的流行&#xff0c;为了提供对IP路由的支持&#xff0c;IETF在相关标准中对IS-IS进行了扩充和修改&#xf…

Vitis HLS 学习笔记--块级控制(IDE 2024.1 + 执行模式 + 默认接口实现)

目录 1. 简介 2. 默认接口实现 2.1 执行模式 2.2 接口范式 2.2.1 存储器 2.2.2 串流 2.3.3 寄存器 2.3 Vitis Kernel Flow 2.3.1 默认的协议 2.3.2 vadd 代码 2.3.3 查看报告 2.4 Vivado IP Flow 2.4.1 默认的协议 2.4.2 vadd 代码 2.4.3 查看报告 3. 测试与波…

红宝书第二十一讲:详解JavaScript的模块化(CommonJS与ES Modules)

红宝书第二十一讲&#xff1a;详解JavaScript的模块化&#xff08;CommonJS与ES Modules&#xff09; 资料取自《JavaScript高级程序设计&#xff08;第5版&#xff09;》。 查看总目录&#xff1a;红宝书学习大纲 一、模块化的意义&#xff1a;分而治之 模块化解决代码依赖混…

github 页面超时解决方法

github 页面超时解决方法 每次好不容易找到github项目代码之后&#xff0c;满心欢喜打开却是个无法访问&#xff0c;心顿时又凉了半截&#xff0c;现在有方法可以访问github啦 快来学习 打开浏览器插件&#xff08;Edge浏览器&#xff09; 搜索iLink插件 并安装 打开插件 填…

前端 vue 项目上线前操作

目录 一、打包分析 二、CDN加速 三、项目部署 1. 打包部署 2. nginx 解决 history 刷新 404 问题 3. nginx配置代理解决生产环境跨域问题 一、打包分析 项目编写完成后&#xff0c;就需要部署到服务器上供他人访问。但是在此之前&#xff0c;我们可以先预览项目的体积大…

vue: easy-cron扩展-更友好地显示表达式

我们一个批处理调度系统里要用到cron表达式&#xff0c;于是就在网上找到一个现成的组件easy-cron&#xff0c;采用后发现&#xff0c;它的配置界面还是很直观的&#xff0c;但显示时直接显示cron表达式&#xff0c;这对业务人员很不友好&#xff0c;所以&#xff0c;我们就扩展…

移动零+复写零+快乐数+盛最多水的容器+有效三角形的个数

前言 2025.3.31&#xff0c;今天开始每日五道算法题&#xff0c;今天的算法题如标题&#xff01; 双指针算法 在做今天的算法题之前&#xff0c;先来介绍一下今天会用到的算法&#xff01; 双指针算法分为了两种常见的形式&#xff1a;对撞指针和快慢指针&#xff01; 对撞…

Linux中常用的文件管理命令

一、文件和目录的建立 文件 touch命令 单一文件的创建 当按下回车后我们就可以在桌面获得一个名字叫file的文件 [rootlocalhost Desktop]# touch file 同步文件访问时间和文件修改时间 由上两图可知touch file这个命令还可以把文件访问时间和文件修改时间变成touch file命…

Root Cause Analysis in Microservice Using Neural Granger Causal Discovery

Root Cause Analysis in Microservice Using Neural Granger Causal Discovery 出处:AAAI 24 摘要 近年来,微服务因其可扩展性、可维护性和灵活性而在 IT 运营中得到广泛采用。然而,由于微服务中的复杂关系,当面临系统故障时,站点可靠性工程师 (SRE) 很难查明根本原…

学习笔记—数据结构—二叉树(链式)

目录 二叉树&#xff08;链式&#xff09; 概念 结构 初始化 遍历 前序遍历 中序遍历 后序遍历 层序遍历 结点个数 叶子结点个数 第k层结点个数 深度/高度 查找值为x的结点 销毁 判断是否为完整二叉树 总结 头文件Tree.h Tree.c 测试文件test.c 补充文件Qu…

深入理解指针5

sizeof和strlen的对比 sizeof的功能 **sizeof是**** 操作符****&#xff0c;用来**** 计算****变量或类型或数组所占**** 内存空间大小****&#xff0c;**** 单位是字节&#xff0c;****他不管内存里是什么数据** int main() {printf("%zd\n", sizeof(char));p…

一文详解QT环境搭建:Windows使用CLion配置QT开发环境

在当今的软件开发领域&#xff0c;跨平台应用的需求日益增长&#xff0c;Qt作为一款流行的C图形用户界面库&#xff0c;因其强大的功能和易用性而备受开发者青睐。与此同时&#xff0c;CLion作为一款专为C/C打造的强大IDE&#xff0c;提供了丰富的特性和高效的编码体验。本文将…

NE 综合实验3:基于 IP 配置、链路聚合、VLAN 管理、路由协议及安全认证的企业网络互联与外网访问技术实现(H3C)

综合实验3 实验拓扑 设备名称接口IP地址R1Ser_1/0与Ser_2/0做捆绑MP202.100.1.1/24G0/0202.100.2.1/24R2Ser_1/0与Ser_2/0做捆绑MP202.100.1.2/24G0/0172.16.2.1/24G0/1172.16.1.1/24G0/2172.16.5.1/24R3G5/0202.100.2.2/24G0/0172.16.2.2/24G0/1172.16.3.1/24G0/2172.16.7.1/…

多段圆弧拟合离散点实现切线连续

使用多段圆弧来拟合一个由离散点组成的曲线,并且保证切线连续。也就是说&#xff0c;生成的每一段圆弧之间在连接点处必须有一阶导数连续&#xff0c;也就是切线方向相同。 点集分割 确保每个段的终点是下一段的起点&#xff0c;相邻段共享连接点&#xff0c;避免连接点位于数…

【蓝桥杯】第十四届C++B组省赛

⭐️个人主页&#xff1a;小羊 ⭐️所属专栏&#xff1a;蓝桥杯 很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~ 目录 试题A&#xff1a;日期统计试题B&#xff1a;01串的熵试题C&#xff1a;冶炼金属试题D&#xff1a;飞机降落试题E&#xff1a;接…

企业级海外网络专线行业应用案例及服务商推荐

在全球化业务快速发展的今天&#xff0c;传统网络技术已难以满足企业需求。越来越多企业开始选择新型海外专线解决方案&#xff0c;其中基于SD-WAN技术的企业级海外网络专线备受关注。这类服务不仅能保障跨国数据传输&#xff0c;还能根据业务需求灵活调整网络配置。接下来我们…