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

这篇具有很好参考价值的文章主要介绍了【算法学习】简单多状态-动态规划。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

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

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

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

【算法学习】简单多状态-动态规划,# 动态规划,学习,动态规划,算法,c++

目录

一、打家劫舍Ⅰ

题目解析:

编码:

二、打家劫舍Ⅱ

题目解析:

编码: 

三、删除并获得点数

题目解析:

编码: 

四、粉刷房子

题目解析:

编码: 

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

题目解析:

编码: 


一、打家劫舍Ⅰ

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

【算法学习】简单多状态-动态规划,# 动态规划,学习,动态规划,算法,c++

题目解析:

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

【算法学习】简单多状态-动态规划,# 动态规划,学习,动态规划,算法,c++

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

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

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

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

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

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

【算法学习】简单多状态-动态规划,# 动态规划,学习,动态规划,算法,c++

对于到达第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)官网 - 全球极客挚爱的技术成长平台

【算法学习】简单多状态-动态规划,# 动态规划,学习,动态规划,算法,c++

题目解析:

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

【算法学习】简单多状态-动态规划,# 动态规划,学习,动态规划,算法,c++

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

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

        我们还是按照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)官网 - 全球极客挚爱的技术成长平台

【算法学习】简单多状态-动态规划,# 动态规划,学习,动态规划,算法,c++

题目解析:

        题目的意思就是可以选择一个位置的点数,获得它,但是需要删除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)官网 - 全球极客挚爱的技术成长平台

【算法学习】简单多状态-动态规划,# 动态规划,学习,动态规划,算法,c++

题目解析:

        实际上也是相邻问题的变种,此时主状态是:到第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)官网 - 全球极客挚爱的技术成长平台

【算法学习】简单多状态-动态规划,# 动态规划,学习,动态规划,算法,c++

题目解析:

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

        因为题目中的信息(必须在再次购买之前出售掉之前的股票),在之前的买卖股票题目中(除开了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) 文章来源地址https://www.toymoban.com/news/detail-801884.html

到了这里,关于【算法学习】简单多状态-动态规划的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 动态规划之简单多状态

    1.题目链接:按摩师 2.题目描述:一个有名的按摩师会收到源源不断的预约请求,每个预约都可以选择接或不接。在每次预约服务之间要有休息时间,因此她不能接受相邻的预约。给定一个预约请求序列,替按摩师找到最优的预约集合(总预约时间最长),返回总的分钟数。

    2024年02月09日
    浏览(34)
  • 「动态规划」简单多状态dp问题

    以经典问题“打家劫舍”来解释简单多状态dp问题和解决方法 题目链接:打家劫舍I 这种问题就是在某一个位置有多个状态可以选择,选择 不同的状态 会影响 最终结果 在这道题中就是小偷在每一个房屋,可以选择偷或不偷,每一次选择都会影响最终偷窃金额 状态表示 因为

    2024年03月15日
    浏览(44)
  • c++--简单多状态动态规划问题

    PS:以下代码均为C++实现 1.按摩师  力扣 一个有名的按摩师会收到源源不断的预约请求,每个预约都可以选择接或不接。在每次预约服务之间要有休息时间,因此她不能接受相邻的预约。给定一个预约请求序列,替按摩师找到最优的预约集合(总预约时间最长),返回总的分钟

    2024年02月14日
    浏览(42)
  • AcWing算法学习笔记:动态规划(背包 + 线性dp + 区间dp + 计数dp + 状态压缩dp + 树形dp + 记忆化搜索)

    算法 复杂度 时间复杂度0(nm) 空间复杂度0(nv) 代码 算法 通过滚动数组对01背包朴素版进行空间上的优化 f[i] 与 f[i - 1]轮流交替 若体积从小到大进行遍历,当更新f[i, j]时,f[i - 1, j - vi] 已经在更新f[i, j - vi]时被更新了 因此体积需要从大到小进行遍历,当更新f[i, j]时,f[i - 1,

    2024年02月21日
    浏览(35)
  • 【动态规划】简单多状态dp问题(2)买卖股票问题

    买卖股票问题 传送门:力扣309. 最佳买卖股票时机含冷冻期 题目: 1.1 题目解析 越难的dp问题,看示例只能起到了解题目的效果,一般推不出啥普遍的规律,所以接下来就是我们的算法原理,通过动归的思想去理解,才会豁然开朗! 1.2 算法原理 1.2.1 状态表示 我们需要通过经

    2024年02月12日
    浏览(47)
  • 算法提高-动态规划-状态机模型

    这题比较简单,主要是学习一下状态机的模版(如何定义状态,dp方程如何推导)。 再学一个知识点:线性dp(i由i-1递推过来)可以用滚动数组优化空间复杂度 限制购买天数 这里也是线性dp,当然可以用滚动数组优化,但是之前大盗阿福写过了,这里就朴素版本了 限制了卖

    2024年02月15日
    浏览(56)
  • C++算法 —— 动态规划(3)多状态

    每一种算法都最好看完第一篇再去找要看的博客,因为这样会帮你梳理好思路,看接下来的博客也就更轻松了。当然,我也会尽量在写每一篇时都可以让不懂这个算法的人也能边看边理解。 动规的思路有五个步骤,且最好画图来理解细节,不要怕麻烦。当你开始画图,仔细阅

    2024年02月09日
    浏览(29)
  • 【动态规划】简单多状态dp问题(1)打家劫舍问题

    打家劫舍问题 传送门:面试题 17.16. 按摩师 题目: 1.1 题目解析 越难的dp问题,看示例只能起到了解题目的效果,一般推不出啥普遍的规律,所以接下来就是我们的算法原理,通过动归的思想去理解,才会豁然开朗! 1.2 算法原理 1.2.1 状态表示 我们需要通过经验 + 题目要求去

    2024年02月12日
    浏览(35)
  • 【动态规划专栏】专题三:简单多状态dp--------3.删除并获得点数

    本专栏内容为:算法学习专栏,分为优选算法专栏,贪心算法专栏,动态规划专栏以及递归,搜索与回溯算法专栏四部分。 通过本专栏的深入学习,你可以了解并掌握算法。 💓博主csdn个人主页:小小unicorn ⏩专栏分类:动态规划专栏 🚚代码仓库:小小unicorn的代码仓库🚚

    2024年03月22日
    浏览(33)
  • 【状态压缩】【动态规划】【C++算法】691贴纸拼词

    视频算法专题 动态规划汇总 状态压缩 我们有 n 种不同的贴纸。每个贴纸上都有一个小写的英文单词。 您想要拼写出给定的字符串 target ,方法是从收集的贴纸中切割单个字母并重新排列它们。如果你愿意,你可以多次使用每个贴纸,每个贴纸的数量是无限的。 返回你需要拼

    2024年01月19日
    浏览(29)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包