算法沉淀 —— 动态规划篇(简单多状态dp问题上)

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

前言

几乎所有的动态规划问题大致可分为以下5个步骤,后续所有问题分析都将基于此

  • 1.、状态表示:通常状态表示分为以下两种,其中更是第一种为主。

    • 以i为结尾,dp[i] 表示什么,通常为代求问题(具体依题目而定)
    • 以i为开始,dp[i]表示什么,通常为代求问题(具体依题目而定)
  • 2、状态转移方程

    • 以上述的dp[i]意义为根据, 通过最近一步来分析和划分问题,由此来得到一个有关dp[i]的状态转移方程。
  • 3、dp表创建,初始化

    • 动态规划问题中,如果直接使用状态转移方程通常会伴随着越界访问等风险,所以一般需要初始化。而初始化最重要的两个注意事项便是:保证后续结果正确,不受初始值影响;下标的映射关系
    • 初始化一般分为以下两种:
      • 直接初始化开头的几个值。
      • 一维空间大小+1,下标从1开始;二维增加一行/一列
  • 4、填dp表、填表顺序:根据状态转移方程来确定填表顺序。

  • 5、确定返回值

一、按摩师

【题目链接】:面试题 17.16. 按摩师
【题目】:

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

【分析】:
 我们定义dp[i]表示从[0, 1]按摩师能找到的最优预约集合。但我们发现第i个位置还可以细分为两种情况:i号预约、i号不预约。因此我们进一步分析定义f[i]表示从开始到i位置,并且i号预约的最优集合;g[i]表示从开始到i位置,并且i号不预约的最优集合。
 其中 f[i] = g[i - 1] + nums[i - 1]g[i] = max(g[i - 1], f[i - 1])
【代码编写】:

class Solution {
public:
    int massage(vector<int>& nums) {
        int n = nums.size();
        vector<int> f(n + 1);//f[i]表示[1, i]之间, 并且i号预约的最优集合
        vector<int> g = f;//g[i]表示[1, i]之间,并且i号不预约的最优集合
        //填表
        for(int i = 1; i <= n; i++)
        {
            g[i] = max(g[i - 1], f[i - 1]);
            f[i] = g[i - 1] + nums[i - 1];
        }
        return max(f[n], g[n]);
    }
};

二、打家劫舍 II

【题目链接】:LCR 090. 打家劫舍 II
【题目】:

 一个专业的小偷,计划偷窃一个环形街道上沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
 给定一个代表每个房屋存放金额的非负整数数组 nums ,请计算 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。

【实例】:

输入:nums = [2,3,2]
输出:3
解释:你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。

【分析】:
 显然题目中所有房屋形成了一个类似于环形结构,比较棘手。所以我们可以以1号房屋是否被偷进行分类讨论,将环形结构蜕变成普通数组,首尾不影响。
 分类情况:因为房屋不能连续被偷。所以:

  1. 如果1号房屋不被偷,此时模型简化为偷[2, n]号房屋,首位不影响。
  2. 如果1号房屋被偷,此时意味着2号和n号房屋一段不被偷,此时模型简化为偷[3, n-1]号房屋,首位不影响。最后结果假设1号房屋所偷金额即可。

 上述两种情况统一为偷[begin, end]号的房屋,相邻房屋不能都被偷。此时我们可以定义dp[i]表示小偷在[begin, i]号房屋间所偷的最大金额。当我们发现i位置还可细分为偷以不透。所以我们继续细分情况,定义f[i]表示小偷在[begin, i]之间,且i号房屋被偷的最大金额;g[i]表示小偷在[begin, i]之间,且i号房屋不被偷的最大金额。
 此时f[i]的最近一步时,i-1号房屋不被偷,i号房屋被偷的最大金额,所以发f[i]的状态转移方程为f[i] = g[i-1] + nums[i-1];对于g[i]来说,此时有两种情况,i-1号房屋被偷和不被偷的最大金额,所以g[i]的状态转移方程为g[i] = max(f[i - 1], g[i - 1]。最后比较f[n],g[n],返回最大值即可。

【代码编写】:

class Solution {
public:
    int rob(vector<int>& nums) {
        int n = nums.size();
        return max(_rob(nums, 2, n), _rob(nums, 3, n - 1) + nums[0]);
    }

    int _rob(vector<int>& nums, int begin, int end)
    {
        int n = nums.size();
        vector<int> f(n + 1);//f[i]表示[begin, i]号房屋中,i号房屋偷,偷窃到的最高金额
        vector<int> g = f;//g[i]表示[begin, i]号房屋中,i号房屋不偷,偷窃到的最高金额
        for(int i = begin; i <= end; i++)
        {
            f[i] = g[i - 1] + nums[i - 1];
            g[i] = max(f[i - 1], g[i - 1]);
        }
        return max(f[end], g[end]);
    }
};

三、删除并获得点数

【题目链接】:740. 删除并获得点数
【题目】:

 给你一个整数数组 nums ,你可以对它进行一些操作。
每次操作中,选择任意一个 nums[i] ,删除它并获得 nums[i] 的点数。之后,你必须删除 所有 等于 nums[i] - 1 和 nums[i] + 1 的元素。
 开始你拥有 0 个点数。返回你能通过这些操作获得的最大点数。

【实例】:

输入:nums = [2,2,3,3,3,4]
输出:9
解释:
删除 3 获得 3 个点数,接着要删除两个 2 和 4 。
之后,再次删除 3 获得 3 个点数,再次删除 3 获得 3 个点数。
总共获得 9 个点数。

【分析】:
 我们可以向通过一个数组hash,将原数组中所有相同元素(假设为x)的值累加后(假设结果为sum),存放到hash中(下标为x, hash[x] = sum)。
 通过第一步,我们可以将原问题转化为:在hash数组中,相邻元素的值不能同时获取,求最终所获得的点数的最大值!
 我们定义f[i]表示在[0, i]中获取点数,并且保证hash[i]的元素被获取,最终所能获得的最大点;g[i]表示在[0, i]中获取点数,并且保证hash[i]的元素不被获取,最终所能获得的最大点数。可得状态转移方程为:f[i] = g[i - 1] + hash[i];g[i] = max(f[i - 1], g[i - 1])
 从左往右依次填dp表后,返回结果最大值!
【代码编写】:

class Solution {
public:
    int deleteAndEarn(vector<int>& nums) {
        const int N = 10001;
        int hash[N] = {0};
        for(auto x : nums)
            hash[x] += x;
        vector<int> f(N);//f[i]表示从[1, i],i位置对应点数被获得,所获得的最大点数
        vector<int> g = f;//g[i]表示从[1, i],i位置对应点数不被获得,所获得的最大点数
        for(int i = 1; i < N; i++)
        {
            f[i] = g[i - 1] + hash[i];
            g[i] = max(f[i - 1], g[i - 1]);
        }
        return max(f[N - 1],g[N - 1]); 
    }
};

四、粉刷房子

【题目链接】:LCR 091. 粉刷房子
【题目】:

 假如有一排房子,共 n 个,每个房子可以被粉刷成红色、蓝色或者绿色这三种颜色中的一种,你需要粉刷所有的房子并且使其相邻的两个房子颜色不能相同。
 当然,因为市场上不同颜色油漆的价格不同,所以房子粉刷成不同颜色的花费成本也是不同的。每个房子粉刷成不同颜色的花费是以一个 n x 3 的正整数矩阵 costs 来表示的。
 例如,costs[0][0] 表示第 0 号房子粉刷成红色的成本花费;costs[1][2] 表示第 1 号房子粉刷成绿色的花费,以此类推。
 请计算出粉刷完所有房子最少的花费成本。

【实例】:

输入: costs = [[17,2,17],[16,16,5],[14,3,19]]
输出: 10
解释: 将 0 号房子粉刷成蓝色,1 号房子粉刷成绿色,2 号房子粉刷成蓝色。
最少花费: 2 + 5 + 3 = 10。

【分析】:
 我们可以定义dp[i]表示粉刷[0, i]号房子,并且相邻房子颜色不同,其花费的最少成本。但我们发现i号防止可以细分为刷成红色、蓝色、绿色。

 所以我们可以使用一个二维数组,其中dp[i][0]表示粉刷[0, i]号房子,且i号房子为蓝色的最小花费;dp[i][1]表示粉刷[0, i]号房子,且i号房子为绿色的最小花费;dp[i][0]表示粉刷[0, i]号房子,且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][0], dp[i - 1][1]) + costs[i - 1][2];

 从左往右依次填表,最后比较dp[n][0]、dp[n][1]、dp[n][2]的最小值即可!
【代码编写】:文章来源地址https://www.toymoban.com/news/detail-847678.html

class Solution {
public:
    int minCost(vector<vector<int>>& costs) {
        int n = costs.size();
        vector<vector<int>> dp(n+1, vector<int>(3));
        //dp[i][0]表示粉刷[0, i]号房子,且i号房子为蓝色的最小花费
        //dp[i][1]表示粉刷[0, i]号房子,且i号房子为绿色的最小花费
        //dp[i][2]表示粉刷[0, i]号房子,且i号房子为红色的最小花费
        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][0], dp[i - 1][1]) + costs[i - 1][2];
        }
        return min(dp[n][0], min(dp[n][1], dp[n][2]));
    }
};

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

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

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

相关文章

  • 【动态规划】简单多状态dp问题(1)打家劫舍问题

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

    2024年02月12日
    浏览(42)
  • 【动态规划】12简单多状态dp问题_打家劫舍II_C++ (medium)

    题目链接:leetcode打家劫舍II 目录 题目解析: 算法原理 1.状态表示 2.状态转移方程 3.初始化 4.填表顺序 5.返回值 编写代码 题目让我们求 在不触动警报装置的情况下  ,能够偷窃到的最高金额。 由题可得: 第一个房屋和最后一个房屋是紧挨着的 如果两间相邻的房屋在同一晚

    2024年02月02日
    浏览(47)
  • 算法沉淀 —— 动态规划篇(路径问题)

    几乎所有的动态规划问题大致可分为以下5个步骤,后续所有问题分析都将基于此 1.、状态表示:通常状态表示分为基本分为以下两种,其中更是以第一种为甚。 以i为结尾 ,dp[i] 表示什么,通常为代求问题(具体依题目而定) 以i为开始 ,dp[i]表示什么,通常为代求问题(具

    2024年04月17日
    浏览(44)
  • 算法沉淀 —— 动态规划(子序列问题(上))

    几乎所有的动态规划问题大致可分为以下5个步骤,后续所有问题分析都将基于此 1.、状态表示:通常状态表示分为以下两种,其中更是第一种为主。 以i为结尾 ,dp[i] 表示什么,通常为代求问题(具体依题目而定) 以i为开始 ,dp[i]表示什么,通常为代求问题(具体依题目而

    2024年04月15日
    浏览(46)
  • 算法沉淀——动态规划篇(子数组系列问题(下))

    几乎所有的动态规划问题大致可分为以下5个步骤,后续所有问题分析都将基于此 1.、状态表示:通常状态表示分为以下两种,其中更是第一种为主。 以i为结尾 ,dp[i] 表示什么,通常为代求问题(具体依题目而定) 以i为开始 ,dp[i]表示什么,通常为代求问题(具体依题目而

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

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

    2024年03月22日
    浏览(43)
  • acwing算法基础之动态规划--数位统计DP、状态压缩DP、树形DP和记忆化搜索

    暂无。。。 暂无。。。 题目1 :求a~b中数字0、数字1、…、数字9出现的次数。 思路:先计算1~a中每位数字出现的次数,然后计算1~b-1中每位数字出现的次数,两个相减即是最终答案。 那么,如何计算1~a中每位数字出现的次数呢? 首先,将a的每一位存入向量num中,例如a=123

    2024年02月04日
    浏览(51)
  • 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日
    浏览(43)
  • 【算法】动态规划(dp问题),持续更新

    介绍本篇之前,我想先用人话叙述一般解决动态规划问题的思路: 动态规划的问题,本身有许多产生结果的可能,需要在具体题目下得到满足某个条件的解。 如何得到呢? 我们就需要根据这个具体问题,建立一个状态表( dp 表 ),在这张 dp 表中的每一个位置的数据都有明

    2024年02月04日
    浏览(48)
  • 【算法学习】简单多状态-动态规划

            本篇博客记录动态规划中的简单多状态问题。         在之前的动态规划类型的题中,我们每次分析的都只是一种或者某一类的状态,定义的dp表也是围绕着一种状态来的。         现在可能对于一种状态,存在几种不同的子状态,在状态转移过程中相互影响。此时

    2024年01月18日
    浏览(40)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包