算法沉淀——动态规划之简单多状态 dp 问题(上)(leetcode真题剖析)

news2025/1/19 23:06:33

在这里插入图片描述

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

  • 01.按摩师
  • 02.打家劫舍 II
  • 03.删除并获得点数
  • 04.粉刷房子

01.按摩师

题目链接:https://leetcode.cn/problems/the-masseuse-lcci/

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

注意:本题相对原题稍作改动

示例 1

输入: [1,2,3,1]

输出: 4

解释:选择 1 号预约和 3 号预约,总时长 = 1 + 3 = 4。

示例 2

输入: [2,7,9,3,1]

输出: 12

解释: 选择 1 号预约、 3 号预约和 5 号预约,总时长 = 2 + 9 + 1 = 12。

示例 3

输入: [2,1,4,5,3,1,1,3]

输出: 12

解释: 选择 1 号预约、 3 号预约、 5 号预约和 8 号预约,总时长 = 2 + 4 + 3 + 3 = 12。

思路

  1. 状态表达: 我们定义两个状态数组,fg

    • f[i] 表示:选择到位置 i 时,此时的最长预约时长,且 nums[i] 必须选。
    • g[i] 表示:选择到位置 i 时,此时的最长预约时长,nums[i] 不选。
  2. 状态转移方程: 对于 f[i]

    • 如果 nums[i] 必须选,那么我们仅需知道 i - 1 位置在不选的情况下的最长预约时长,然后加上 nums[i] 即可,因此 f[i] = g[i - 1] + nums[i]

    对于 g[i]

    • 如果 nums[i] 不选,那么 i - 1 位置上选或者不选都可以。因此,我们需要知道 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])

代码

class Solution {
public:
    int massage(vector<int>& nums) {
        int n = nums.size();
        if(n==0) return 0;
        vector<int> f(n);
        vector<int> g(n);
        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(g[n - 1], f[n - 1]);
    }
};

02.打家劫舍 II

题目链接:https://leetcode.cn/problems/house-robber-ii/

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警

给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。

示例 1:

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

示例 2:

输入:nums = [1,2,3,1]
输出:4
解释:你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
     偷窃到的最高金额 = 1 + 3 = 4 。

示例 3:

输入:nums = [1,2,3]
输出:3

提示:

  • 1 <= nums.length <= 100
  • 0 <= nums[i] <= 1000

思路

将环形的打家劫舍问题转化为两个单排的问题。具体来说,你分别考虑两种情况:

a. 偷第一个房屋的情况: 在这种情况下,由于首尾相连,你不能偷最后一个房子,因此偷窃范围是 [0, n - 2]。你可以使用之前解决「打家劫舍I」的动态规划方法来找到在这个范围内的最大金额,得到的结果是 x

b. 不偷第一个房屋的情况: 在这种情况下,你可以偷最后一个房子,因此偷窃范围是 [1, n - 1]。同样,使用相同的动态规划方法得到在这个范围内的最大金额,得到的结果是 y

最终的答案就是这两种情况下的最大值,即 max(x, y)

代码

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 start,int end){
        if(start>end) return 0;

        int n=nums.size();
        vector<int> f(n);
        vector<int> g(n);

        f[start]=nums[start];
        for(int i=start+1;i<=end;i++){
            f[i]=g[i-1]+nums[i];
            g[i]=max(g[i-1],f[i-1]);
        }

        return max(g[end],f[end]);
    }
};

03.删除并获得点数

题目链接:https://leetcode.cn/problems/delete-and-earn/

给你一个整数数组 nums ,你可以对它进行一些操作。

每次操作中,选择任意一个 nums[i] ,删除它并获得 nums[i] 的点数。之后,你必须删除 所有 等于 nums[i] - 1nums[i] + 1 的元素。

开始你拥有 0 个点数。返回你能通过这些操作获得的最大点数。

示例 1:

输入:nums = [3,4,2]
输出:6
解释:
删除 4 获得 4 个点数,因此 3 也被删除。
之后,删除 2 获得 2 个点数。总共获得 6 个点数。

示例 2:

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

提示:

  • 1 <= nums.length <= 2 * 104
  • 1 <= nums[i] <= 104

思路

其实这道题可以看作是「打家劫舍I」问题的变体。通过将每个数字的出现的和记录在 hash 数组中,然后在 hash 数组上应用「打家劫舍」的思路,你能够有效地解决这个问题。

具体来说,可以创建一个大小为 10001(根据题目的数据范围)的 hash 数组,将 nums 数组中的每个元素 x 累加到 hash 数组下标为 x 的位置上。然后就可以使用「打家劫舍I」问题的动态规划方法,从 hash 数组中找到不相邻的元素的最大和。

代码

class Solution {
public:
    int deleteAndEarn(vector<int>& nums) {
        int hash[10001] = {0};
        for(int& x:nums) hash[x]+=x;

        vector<int> f(10001);
        vector<int> g(10001);

        for(int i=1;i<10001;++i){
            f[i]=g[i-1]+hash[i];
            g[i]=max(g[i-1],f[i-1]);
        }

        return max(f[10000],g[10000]);
    }
};

04.粉刷房子

题目链接:https://leetcode.cn/problems/JEj789/

假如有一排房子,共 n 个,每个房子可以被粉刷成红色、蓝色或者绿色这三种颜色中的一种,你需要粉刷所有的房子并且使其相邻的两个房子颜色不能相同。

当然,因为市场上不同颜色油漆的价格不同,所以房子粉刷成不同颜色的花费成本也是不同的。每个房子粉刷成不同颜色的花费是以一个 n x 3 的正整数矩阵 costs 来表示的。

例如,costs[0][0] 表示第 0 号房子粉刷成红色的成本花费;costs[1][2] 表示第 1 号房子粉刷成绿色的花费,以此类推。

请计算出粉刷完所有房子最少的花费成本。

示例 1:

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

示例 2:

输入: costs = [[7,6,2]]
输出: 2 

提示:

  • costs.length == n
  • costs[i].length == 3
  • 1 <= n <= 100
  • 1 <= costs[i][j] <= 20

思路

  1. 状态表表示:

    • 在处理线性动态规划时,采用“经验+题目要求”方式定义状态表,选择以某个位置为结尾的方式。
    • 在该位置结束时,定义三种颜色选择的状态表,分别表示最后一个位置选择“红色”、“蓝色”和“绿色”的最小花费。
  2. 状态转移方程:

    • 分析三个状态的转移方程,以 dp[i][0] 为例:

      • 若选择在位置 i 粉刷“红色”,考虑前一个位置“蓝色”和“绿色”两种情况的最小花费,再加上当前位置的花费。
      • 类似地,对于 dp[i][1] dp[i][2],分别考虑选择“蓝色”和“绿色”时的最小花费。

      于是状态方程为:

      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];
      
  3. 初始化:

    • 添加一个辅助节点,将其初始化为 0,确保后续填表的正确性。
    • 注意辅助节点的值要符合题目的要求。
  4. 填表顺序:

    • 根据状态转移方程,从左往右同时填充三个表格。
  5. 返回值:

    • 返回最后一个位置三种颜色选择的最小值,即 min(dp[n][0], min(dp[n][1], dp[n][2]))

代码

class Solution {
public:
    int minCost(vector<vector<int>>& costs) {
        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][0],dp[i-1][1])+costs[i-1][2];
        }

        return min(dp[n][0],min(dp[n][1],dp[n][2]));
    }
};

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

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

相关文章

软件工程复习笔记

一、软件工程概述 软件 = 程序 + 数据 + 相关文档 软件危机(Software Crisis) 指由于落后的软件生产方式无法满足迅速增长的计算机软件需求,从而导致软件开发与维护过程中出现一系列严重问题的现象。 软件工程三要素 方法、工具、过程 软件工程目标 在给定成本、进度的…

【Linux】 yum命令使用

yum命令 yum&#xff08; Yellow dog Updater, Modified&#xff09; 是一个在 Fedora、CentOS 及其它一些基于 RPM 的 Linux 发行版中使用的包管理器。它允许用户自动安装、更新、配置和删除软件包。yum 由 Python 写成&#xff0c;基于 RPM&#xff08;Red Hat Package Mana…

Vue3路由组件练习

Vue3 路由组件练习 演示效果代码分析 安装 vue-router创建路由文件创建路由实例使用 router-link 组件导航 代码实现 index.js 文件App 文件 1. 演示效果 2. 代码分析 2.1. 安装 vue-router 命令&#xff1a;npm i vue-router 应用插件&#xff1a;Vue.use(VueRouter) 2.2…

linux僵尸进程

僵尸进程&#xff08;Zombie Process&#xff09;是指在一个进程终止时&#xff0c;其父进程尚未调用wait()或waitpid()函数来获取该进程的终止状态信息&#xff0c;导致进程的资源&#xff08;如进程表中的记录&#xff09;仍然保留在系统中的一种状态。 当一个进程结束时&am…

C语言数据存储

目录 一.数据类型的介绍 &#xff08;1&#xff09;整形家族 &#xff08;2&#xff09;浮点型家族 &#xff08;3&#xff09;构造类型 &#xff08;4&#xff09;其他 二.整形在内存中如何进行存储 &#xff08;1&#xff09;原&#xff0c;反&#xff0c;补 &#xf…

Spring解决循环依赖

目录 什么是spring循环依赖 什么情况下循环依赖可以被处理&#xff1f; spring 如何解决循环依赖 创建A这个Bean的流程 答疑 疑问&#xff1a;在给B注入的时候为什么要注入一个代理对象&#xff1f; 初始化的时候是对A对象本身进行初始化&#xff0c;而容器中以及注入到B…

三维模型轻量化、格式转换、可视化、数字孪生综合服务平台

老子云概述 老子云3D可视化快速开发平台&#xff0c;集云压缩、云烘焙、云存储云展示于一体&#xff0c;使3D模型资源自动输出至移动端PC端、Web端&#xff0c;能在多设备、全平台进行展示和交互&#xff0c;是全球领先、自主可控的自动化3D云引擎。 平台架构 平台特性 基于 …

Linux:gitlab创建组,创建用户,创建项目

创建组和项目 让后可以在组里创建一个个仓库 创建成员 我创建个成员再把他分配进这个组里 进入管理员 密码等会我们创建完用户再去配置密码 Regular是普通的用户&#xff0c;只可以正常去访问指定规则的项目 而下面的administrator就是管理员&#xff0c;可以随便进项目&…

[HTML]Web前端开发技术30(HTML5、CSS3、JavaScript )JavaScript基础——喵喵画网页

希望你开心,希望你健康,希望你幸福,希望你点赞! 最后的最后,关注喵,关注喵,关注喵,佬佬会看到更多有趣的博客哦!!! 喵喵喵,你对我真的很重要! 目录 前言 网页标题:手机批发业务-商品备选区<

FPS游戏漫谈玩家的延时都不一样的情况下是如何做到游戏的同步性

在FPS游戏中&#xff0c;保持游戏的同步性是非常重要的&#xff0c;因为游戏中的每个玩家都必须看到其他玩家的动作与行为&#xff0c;同时也需要感受到自己的行为在游戏中的影响。为了保持游戏的同步性&#xff0c;FPS游戏通常采用一种叫做“客户端-服务器”&#xff08;clien…

更改QTabWidget的选项卡的位置

选项卡位置函数&#xff1a; QTabWidget::setTabPosition(QTabWidget::North); //默认为上面 上北下南 参数&#xff1a; QTabWidget::North //上面 QTabWidget::South); //下面 QTabWidget::West //左侧 QTabWidget::East)//右侧 选项卡外观函数&#xff1a; QTabWidget::setT…

C/C++的内存管理(2)——new与delete的内核与本质

内存管理 operator new 与 operator delete函数回看new与delete的实现内置类型自定义类型 常见面试题 我们已经知道了new与delete的用法及其好处&#xff0c;发现它似乎与C语言中的动态内存开辟的函数&#xff08;malloc/calloc/realloc&#xff09;不同 在这里我们特别指出&am…

EasyRecovery2024电脑版软件评测与使用教程

一、EasyRecovery电脑版软件评测 EasyRecovery电脑版是一款功能强大、操作简便的数据恢复软件。它适用于多种场景&#xff0c;无论是误删除、格式化、分区丢失还是硬件故障&#xff0c;都能提供有效的恢复方案。该软件界面直观&#xff0c;即便没有技术背景的用户也能轻松完成…

C语言第三十弹---自定义类型:结构体(上)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】 结构体 1、结构体类型的声明 1.1、结构体回顾 1.1.1、结构的声明 1.1.2、结构体变量的创建和初始化 1.2、结构的特殊声明 1.3、结构的自引用 2、结构体内存…

就业攻略:ChatGPT为您解析职场秘诀!【文章底部添加可得内推码汇总表】

目录 优化个人简历和求职信 提供面试准备和技巧 提供职业规划和发展建议 提供职场生存技巧和建议 作为一种强大的自然语言处理工具 在当今竞争激烈的就业市场中&#xff0c;成功找到理想的工作并不容易。职场新人常常感到困惑&#xff0c;不知道如何才能在众多竞争者中脱颖…

机器学习基础(六)TensorFlow与PyTorch

导语&#xff1a;上一节我们详细探索了监督与非监督学习的结合使用。&#xff0c;详情可见&#xff1a; 机器学习基础&#xff08;五&#xff09;监督与非监督学习的结合-CSDN博客文章浏览阅读4次。将监督学习和非监督学习结合起来&#xff0c;就像将两种不同的艺术形式融合&a…

OpenAI 全新发布文生视频模型 Sora,功能有多强大?

卷疯了卷疯了&#xff0c;短短十几小时内&#xff0c;OpenAI和谷歌接连发布核弹级成果。 国内还没睡的人们&#xff0c;经历了过山车般的疯狂一晚。 就在刚刚&#xff0c;OpenAI突然发布首款文生视频模型——Sora。简单来说就是&#xff0c;AI视频要变天了&#xff01; PS&a…

《高效使用Redis》- 由面试题“Redis是否为单线程”引发的思考

由面试题“Redis是否为单线程”引发的思考 很多人都遇到过这么一道面试题&#xff1a;Redis是单线程还是多线程&#xff1f;这个问题既简单又复杂。说他简单是因为大多数人都知道Redis是单线程&#xff0c;说复杂是因为这个答案其实并不准确。 难道Redis不是单线程&#xff1f…

Linux进程【补充】

文章目录 进程概念task_struct 进程创建forkvfork写时拷贝 进程状态僵尸进程孤儿进程守护进程 进程地址空间是什么为什么怎么做 进程概念 进程是一个程序的执行实例或者是担当系统资源分配的实体。当一个程序运行时&#xff0c;被从硬盘加载到内存中&#xff0c;操作系统为每个…

你真的了解@Async吗?

你真的了解Async吗&#xff1f; 使用场景&#xff1a; 开发中会碰到一些耗时较长或者不需要立即得到执行结果的逻辑&#xff0c;比如消息推送、商品同步等都可以使用异步方法&#xff0c;这时我们可以用到Async。但是直接使用 Async 会有风险&#xff0c;当我们没有指定线程池…