【算法学习】简单多状态-动态规划

前言

        本篇博客记录动态规划中的简单多状态问题。

        在之前的动态规划类型的题中,我们每次分析的都只是一种或者某一类的状态,定义的dp表也是围绕着一种状态来的。

        现在可能对于一种状态,存在几种不同的子状态,在状态转移过程中相互影响。此时需要多个dp表相互进行状态转移。

目录

一、打家劫舍Ⅰ

题目解析:

编码:

二、打家劫舍Ⅱ

题目解析:

编码: 

三、删除并获得点数

题目解析:

编码: 

四、粉刷房子

题目解析:

编码: 

五、买卖股票的最佳时期Ⅳ

题目解析:

编码: 


一、打家劫舍Ⅰ

题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

题目解析:

        根据题目,我们以实例一为例:

        不同颜色的表格表示不同的房子,内显示的$数就是对应的资金,红色表示相邻房子如果同时被盗窃会触发报警。

        现在小偷需要在不触发报警的条件下,一夜之内偷盗的最高金额(这一排房子)。

        我们可以将题目问的问题根据表格翻译以下:从这一排表格中,在不连续取两个相邻各自内数的条件下,能够取到的最高数值和。

        对于上述示例,小偷可以先取1(0号位置),然后取3(2号位置),就能够获得最高金额了。

        对于动态规划题目,我们首先应该确定一个状态表示。根据题目,很简单的就可以确定:到达第i个房子后偷得得最高金额。 

        但是,根据题目的限制条件,影响着能否对该房子盗取的因素:相邻不能同时盗取。而这个取决于你是否对之前的房子进行了盗取。

对于到达第i个房子后偷得得最高金额状态存在两个子状态:

1.对当前房子偷取后得总共最高金额。f[i]

2.对当前房子不偷后得到得总最高金额。g[i] 

        这个两个状态相互制约,所以我们需要不同的dp表对其进行表示 ,可以采用上面的f和g。

        确定好状态表示后,我们就需要确认状态转移方程了。

此处的状态简单,可以直接进行分析。

对于f状态(偷窃此房子),就近分析,上一次(相邻的房子)可以偷窃吗?不行,偷窃的话就会被逮住,所以只能上一次没有偷窃,加上这次偷窃的金额即可。

对于g状态(不偷窃此房子),上一次可以偷窃也可以没有偷窃,那么要得到最高,需要求max。

        所以,我们的状态转移方程对于两个子状态均存在:

f[i] = g[i - 1] + nums[i];

g[i] = max(f[i - 1], g[i - 1]);

其中,nums[i]表示此次房子内的金额数。

        为了防止越界,我们可以对f[0]和g[0]初始化即可。

        f[0]表示对第一间房子偷:那么就是nums[0]即可;g[0]不偷就是0。

        填表顺序自然是从左到右,并且两个dp表同时初始化。返回值就是max(f[n - 1], g[n - 1])即可。

编码:

class Solution {
public:
    int rob(vector<int>& nums) {
        int n = nums.size();
        // 创建dp表
        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(g[i - 1], f[i - 1]);
        }
        return max(f[n - 1], g[n - 1]);
    }
};

时间复杂度:O(n);

空间复杂度:O(n); 

二、打家劫舍Ⅱ

题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

题目解析:

        根据题意,我们以示例2为例子,可以得到下面这张图:

        可以发现,此时,小偷从一横排变成了一个环形的偷法。此时相邻的两间还是存在红色的报警器。

        那么现在,小偷该如何去偷使其钱最大呢?

        我们还是按照1的思路,设置一个总的状态表示:小偷偷盗i间房子为止,偷窃到的最高金额。

        还是和上题一样,此状态细分存在子状态。就是此时对此间房子偷还是不偷。但是和打家劫舍Ⅰ不同的是,如果你偷了第一间房子,那么最后一间房是不能偷的

        我们可以对此不同的条件进行一个分析:也就是说,如果小偷偷了第一间房子,那么除开第一间、第二间房子和最后一间房子,其余都是Ⅰ的逻辑。同理,如果不偷,那么从第二间房子开始就都是Ⅰ的逻辑。

        很巧妙,我们可以分情况,将特殊的情况进行分开就可以进行正常的动态规划环节。

        剩下的分析和Ⅰ一致,只不过需要注意初始化和映射问题,详细可以看编码区别。

编码: 

class Solution {
public:
    // 子函数,用于求一个范围内的最高金额,相邻之间不可同时偷
    int _rob(vector<int>& nums, int start, int end)
    {
        if (end < start) return 0;

        int n = end - start + 1;
        // 创建dp表
        vector<int> f(n);
        auto g = f;

        // 初始化
        f[0] = nums[start];
        // 填表
        for (int i = 1; i < n; ++i)
        {
            f[i] = g[i - 1] + nums[start + i];
            g[i] = max(g[i - 1], f[i - 1]);
        }
        return max(g[n - 1], f[n - 1]);
    }
    int rob(vector<int>& nums) {
        int n = nums.size();
        // 分情况,将环形拆分
        // 1 第一个房子偷
        int x = nums[0];
        x += _rob(nums, 2, n - 2);
        // 第一个房子不偷
        int y = 0;
        y += _rob(nums, 1, n - 1);
        return max(x, y);
    }
};

时间复杂度:O(n)

空间复杂度:O(n) 

三、删除并获得点数

题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

题目解析:

        题目的意思就是可以选择一个位置的点数,获得它,但是需要删除nums[i] + 1和nums[i] - 1的所有值(不能获取)。

        从获取和不获取以及+1和-1,你看是不是和相邻间隔不能同时获取相像呢?现在有个问题,nums的数组似乎不是连续的。

        如果我们能够将nums的值转换为一个下标,对应数组的值就是对应下标在nums中的之和。这样是不是就可以转换为打家劫舍问题?

        下标不存在的对应值为0即可。我们以示例2为例,转换一下:

nums = [2, 2, 3, 3, 3, 4]

转换为对应数组num

num =[0, 0, 4, 9, 4]

        可以转换为打家劫舍问题,连续的并且间隔不能想偷:

        9

        根据题目条件,因为数组的最大值不超过10^4,所以我们可以设置数组大小为10001即可,第一次的时候按照对应值进行映射,随后转换为上面的解法即可。

        详情请看编码。

编码: 

class Solution {
public:
    int deleteAndEarn(vector<int>& nums) {
        int n = nums.size();
        const int N = 10001;
        // 预处理,转换为打家劫舍1
        int nums2[N] = {0};
        for(auto x : nums) nums2[x] += x;

        // 动归问题现在开始!
        // 创建dp表
        vector<int> f(N);
        auto g = f;
        // 填表
        for (int i = 1; i < N; ++i)
        {
            f[i] = g[i - 1] + nums2[i];
            g[i] = max(f[i - 1], g[i - 1]);
        }
        return max(f[N - 1], g[N - 1]);
    }
};

时间复杂度:O(10^4)

空间复杂度:O(10^4) 

四、粉刷房子

题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

题目解析:

        实际上也是相邻问题的变种,此时主状态是:到第i间房子的花费最小成本。

        子状态分别是:到第i间房子刷红色的最小成本、到第i间房子刷蓝色的最小成本、到第i间房子刷绿色的最小成本。

        而相邻之间不能同时的限制条件则变成了颜色不能相同,那么状态转移也就变成了另外两种颜色上次粉刷的最小成本罢了。

        因为存在三种状态,我们可以设置dp表为一个n*3的2维数组。那么状态转移方程为:

dp[i][0] = costs[i][0] + min(dp[i - 1][1], dp[i - 1][2]); 红色

dp[i][1] = costs[i][1] + min(dp[i - 1][0], dp[i - 1][2]); 蓝色

dp[i][2] = costs[i][2] + min(dp[i - 1][1], dp[i - 1][0]); 绿色

        需要初始化dp[0][]罢了,后面的0、1、2分别对应costs[0][0]、costs[0][1]、costs[0][2]即可。

编码: 

class Solution {
public:
    int minCost(vector<vector<int>>& costs) {
        int n = costs.size();
        // 创建dp表
        vector<vector<int>> dp(n, vector<int>(3));  // 红色 0 蓝色1 绿色2
        // 初始化
        dp[0][0] = costs[0][0];
        dp[0][1] = costs[0][1];
        dp[0][2] = costs[0][2];
        // 填表
        for (int i = 1; i < n; ++i)
        {
            dp[i][0] = costs[i][0] + min(dp[i - 1][1], dp[i - 1][2]);
            dp[i][1] = costs[i][1] + min(dp[i - 1][0], dp[i - 1][2]);
            dp[i][2] = costs[i][2] + min(dp[i - 1][1], dp[i - 1][0]);
        }
        return min(dp[n - 1][0], min(dp[n - 1][1], dp[n - 1][2]));
    }
};

五、买卖股票的最佳时期Ⅳ

题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

题目解析:

        此题是买卖股票的最佳时机的变种。

        因为题目中的信息(必须在再次购买之前出售掉之前的股票),在之前的买卖股票题目中(除开了k笔交易,意思是可以无限交易),存在两种子状态:1.拥有股票,2.没有股票。针对这两种子状态进行分析即可。

        但是此题限制了条件:最多可以完成k次交易

        那么状态就可以细化为第i次交易拥有股票状态和第i次交易没有股票状态(0<=i<=k)。

        所以对应的,我们给原本的两个子状态更上k次交易这个维度即可。并且注意到,卖掉的那一刻可以算作一次交易。(和后面的状态转移方程式密切相关)

        所以状态可以表示为:

f[i][j]:以i结尾,拥有股票此时第j笔交易的最大利润。

g[i][j]:以i结尾,没有股票此时第j笔交易的最大利润。

        有了状态,我们可以就近原则去想状态转移方程。

        之前的股票交易题我们是考虑到当天如果有股票,那么前一天要么没股票,今天买,要么前一天有股票,今天不买。当天如果没有股票,要么前一天没股票,今天也不买,要么前一天有股票,今天卖了。

        而这次,只是加上了一个交易次数维度:当天有股票,是第j次交易,那么前一天要么没股票,今天买了。要么前一天有股票,今天没动;当天没股票,是第j次交易,要么前一天没有股票,今天没买,要么前一天有股票,第j-1次交易,今天卖了(因为今天卖了的缘故,所以交易数得增加)

        分析到状态可以很简单的得到下面的状态转移方程

f[i][j] = max(f[i-1][j], g[i-1] - prices[i]);

g[i][j] = max(g[i-1][j], f[i-1][j-1] + prices[i]);

        得到状态转移方程后我们需要对其进行初始化。因为存在i-1和j-1.所以需要对第一行和第一列进行初始化。

        对于第一行,第一天不可能存在交易次数。因为存在交易次数的话,说明买了又卖了,没有任何意义。所以第一行对于拥有股票来说第一个元素(0次交易)初始化为对应股票的负数即可,其余设置为最小值不可影响其他状态(对于最小值,一般编程中习惯设置为-0x3f3f3f3f,最大值反过来即可)。没有股票表示啥也没干,第一个元素初始化为0即可

        对于第一列,表示此时是第0笔交易,是没有之前的交易的,那么我们细看状态转移方程的话,只有没有股票状态表示的时候用到,可以利用在填表过程中设置条件来跳过初始化(j>0)即可,这也是一个很好的技巧。

        返回值的时候实在所有交易次数中判断最小即可(一般最后一次卖出股票肯定是比拥有股票最大利润最优的,所以只需要在卖出股票的几种交易情况中进行对比即可)

        另外,因为交易次数是题目中给的,我们存在对交易次数的优化方案:因为交易一次是值买一次卖一次。在最大利润的情况下是一天买,一天卖。所以交易次数最大不能超过总天数的一半

编码: 

class Solution {
public:
    const int MIN = -0x3f3f3f3f;
    int maxProfit(int k, vector<int>& prices) {
        int n = prices.size();
        // 处理细节问题
        k = min(k, n / 2);
        // 创建dp表
        vector<vector<int>> f(n, vector<int>(k + 1, MIN));
        auto g = f;
        // 初始化
        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] = max(f[i - 1][j], g[i - 1][j] - prices[i]);
                g[i][j] = g[i - 1][j];
                if (j > 0) g[i][j] = max(g[i - 1][j], f[i - 1][j - 1] + prices[i]);
            }
        }
        // 返回值
        int res = g[n - 1][0];
        for (int i = 1; i <= k; ++i) res = max(res,g[n - 1][i]);
        return res; 
    }
};

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

空间复杂度:O(n*k) 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

面试经验分享 | 通关某公司面试靶场

本文由掌控安全学院 - 冰封小天堂 投稿 0x00:探测IP 首先打开时候长这个样&#xff0c;一开始感觉是迷惑行为&#xff0c;试了试/admin&#xff0c;/login这些发现都没有 随后F12查看网络&#xff0c;看到几个js文件带有传参&#xff0c;就丢sqlmap跑了一下无果 随后也反查了…

网络模型及传输基本流程

1.OSI 七层模型 OSI &#xff08; Open System Interconnection &#xff0c;开放系统互连&#xff09;七层网络模型称为开放式系统互联参考模型&#xff0c;是一个逻辑上的定义和规范; 把网络从逻辑上分为了 7 层 . 每一层都有相关、相对应的物理设备&#xff0c;比如路由器…

【C语言】Debian安装并编译内核源码

在Debian 10中安装并编译内核源码的过程如下&#xff1a; 1. 安装依赖包 首先需要确保有足够的权限来安装包。为了编译内核&#xff0c;需要有一些基础的工具和库。 sudo apt update sudo apt upgrade sudo apt install build-essential libncurses-dev bison flex libssl-d…

【分享】windows11 vmware centos7 搭建k8s完整实验

概述 开年第一天&#xff0c;补充下自己的技术栈。 参考文章: k8s安装 - 知乎 【Kubernetes部署篇】K8s图形化管理工具Dasboard部署及使用_k8s可视化管理工具-CSDN博客 centos7环境下安装k8s 1.18.0版本带dashboard界面全记录&#xff08;纯命令版&#xff09;_sysconfig1.…

Keras可以使用的现有模型

官网&#xff1a;https://keras.io/api/applications/ 一些使用的列子&#xff1a; ResNet50&#xff1a;分类预测 import keras from keras.applications.resnet50 import ResNet50 from keras.applications.resnet50 import preprocess_input, decode_predictions import nu…

基于scrapy框架的单机爬虫与分布式爬虫

我们知道&#xff0c;对于scrapy框架来说&#xff0c;不仅可以单机构建复杂的爬虫项目&#xff0c;还可以通过简单的修改&#xff0c;将单机版爬虫改为分布式的&#xff0c;大大提高爬取效率。下面我就以一个简单的爬虫案例&#xff0c;介绍一下如何构建一个单机版的爬虫&#…

修改vue-layer中title

左侧目录树点击时同步目录树名称 试了很多方法 layer.title(新标题&#xff0c;index)不知道为啥不行 最后用了获取html树来修改了 watch: {$store.state.nowTreePath: function(newVal, oldVal) {if (document.querySelectorAll(".lv-title") && document.q…

AD高速板常见问题和过流自锁

可以使用电机减速器来增大电机的扭矩&#xff0c;低速运行的步进电机更要加上减速机 减速电机就是普通电机加上了减速箱&#xff0c;这样便降低了转速增大了扭矩 HDMI布线要求&#xff1a; 如要蛇形使其等长&#xff0c;不要在HDMI的一端绕线。 HDMI走线时两边拉线&#xff0…

见智未来:数据可视化引领智慧城市之潮

在数字时代的浪潮中&#xff0c;数据可视化崭露头角&#xff0c;为打造智慧城市注入了强大的活力。不再被深奥的数据所束缚&#xff0c;我们通过数据可视化这一工具&#xff0c;可以更加接近智慧城市的未来。下面我就以可视化从业者的角度来简单聊聊这个话题。 数据可视化首先为…

wps快速生成目录及页码设置(自备)

目录 第一步目录整理 标题格式设置 插入页码&#xff08;罗马和数字&#xff09; 目录生成&#xff08;从罗马尾页开始&#xff09; ​编辑目录格式修改 第一步目录整理 1罗马标题 2罗马标题1一级标题 1.1 二级标题 1.2二级标题2一级标题 2.1 二级标题 2.2二级标题3一级标…

HTML5+CSS3+JS小实例:锥形渐变彩虹按钮

实例:锥形渐变彩虹按钮 技术栈:HTML+CSS+JS 效果: 源码: 【HTML】 <!DOCTYPE html> <html lang="zh-CN"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /…

【ansible】认识ansible,了解常用的模块

目录 一、ansible是什么&#xff1f; 二、ansible的特点&#xff1f; 三、ansible与其他运维工具的对比 四、ansible的环境部署 第一步&#xff1a;配置主机清单 第二步&#xff1a;完成密钥对免密登录 五、ansible基于命令行完成常用的模块学习 模块1&#xff1a;comma…

huggingface库LocalTokenNotFoundError:需要提供token

今天刚开始学习huggingface&#xff0c;跑示例的时候出了不少错&#xff0c;在此记录一下&#xff1a; (gpu) F:\transformer\transformers\examples\pytorch\image-classification>.\run.bat Traceback (most recent call last):File "F:\transformer\transformers\e…

6.s081 学习实验记录(七)Multithreading

文章目录 一、Uthread: switching between threads简介提示实验代码实验结果 二、Using threads简介实验代码 三、Barrier简介实验代码实验结果 一、Uthread: switching between threads 简介 切换到 thread 分支 git fetchgit checkout threadmake clean 实现用户态线程的…

SHOT特征描述符、对应关系可视化以及ICP配准

一、SHOT特征描述符可视化 C #include <pcl/point_types.h> #include <pcl/point_cloud.h> #include <pcl/search/kdtree.h> #include <pcl/io/pcd_io.h> #include <pcl/features/normal_3d_omp.h>//使用OMP需要添加的头文件 #include <boo…

考完PMP如何让学习价值最大化?考PRINCE2!

01什么是PRINCE2 PRINCE2的全称是Project IN Controlled Environment。也就是受控环境下的项目管理&#xff0c;国际项目管理师认证&#xff0c;在国际上被称为王者认证。PRINCE2描述了如何以一种逻辑性的、有组织的方法&#xff0c;按照明确的步骤对项目进行管理。 95%以上全…

软件实例分享,酒店酒水寄存管理系统软件教程

软件实例分享&#xff0c;酒店酒水寄存管理系统软件教程 一、前言 以下软件教程以 佳易王酒水寄存管理系统软件V16.0为例说明 软件文件下载可以点击最下方官网卡片——软件下载——试用版软件下载 1、寄存的商品名称可以预先设置 2、寄存人可以使用手.机号识别 3、会员充值…

C#,计算几何,贝塞耳插值(Bessel‘s interpolation)的算法与源代码

Friedrich Wilhelm Bessel 1 贝塞耳插值&#xff08;Bessels interpolation&#xff09; 首先要区别于另外一个读音接近的插值算法&#xff1a;贝塞尔插值&#xff08;Bzier&#xff09;。 &#xff08;1&#xff09;读音接近&#xff0c;但不是一个人&#xff1b; &#x…

嵌入式调试工具之GDB

在单片机开发中&#xff0c;我们可以通过集成式的IDE 来进行调试&#xff0c;比如 MDK、IAR 等。 GDB 工具是 GNU 项目调试器&#xff0c;基于命令行使用。和其他的调试器一样&#xff0c;可使用 GDB工具单步运行程序、单步执行、跳入/跳出函数、设置断点、查看变量等等&#…

基于SSM的宁夏旅游网站平台(有报告)。Javaee项目。ssm项目。

演示视频&#xff1a; 基于SSM的宁夏旅游网站平台&#xff08;有报告&#xff09;。Javaee项目。ssm项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring …