算法03贪心与动态规划

算法03贪心与动态规划

  • 1. 贪心与动态规划概述
    • 1.贪心
      • 1.1贪心概述
      • 1.2贪心常用场景
      • 1.3贪心解题思路
    • 2.动态规划
      • 2.1动态规划概述
      • 2.2动态规划常用场景
      • 2.3动态规划解题模板
    • 3.浅谈贪心与动态规划的关系
  • 2.贪心经典题目
    • 区间问题
  • 3.动态规划经典题目
    • 3.1体会“从整体到局部”的思考过程
    • 3.2体会“状态转移方程”系列
      • (1)力扣309.买卖股票的最佳时期含冷冻期与
      • (3)力扣714.买卖股票的最佳时期含手续费
    • 3.3 子序列问题
      • (1)
    • 3.4体会什么时候用一维dp数组,什么时候使用多维dp数组
      • (1)力扣718.最长重复子数组
    • 3.5体会“遍历顺序”
    • 体会“背包问题”
    • 树形dp
    • 编辑距离问题
  • 4.扩展
    • 优先级队列
      • 大顶堆
  • 参考资料:

1. 贪心与动态规划概述

1.贪心

1.1贪心概述

贪心算法(英语:greedy algorithm),又称贪婪算法,是一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是最好或最优的算法。比如在旅行推销员问题中,如果旅行员每次都选择最近的城市,那这就是一种贪心算法。

1.2贪心常用场景

贪心算法在有最优子结构的问题中尤为有效。最优子结构的意思是局部最优解能决定全局最优解。简单地说,问题能够分解成子问题来解决,子问题的最优解能递推到最终问题的最优解。

常用场景如下:

  • 区间问题:贪心算法可以用于解决最小覆盖问题、区间选取问题等
  • 小数背包问题:不同于0-1背包问题(动态规划),物品是可以分割,只装进背包一部分
  • 图论中的问题:例如,Prim和Kruskal算法用于找到图的最小生成树,Dijkstra算法用于找到单源最短路径
  • 与常识紧密结合的问题:分饼干、找零问题等

1.3贪心解题思路

对于贪心相关的算法题,其实没有一个比较固定的模板,常常和经验和常识有关,但这里还是做一个结构化的总结:

1.有些问题的最优解,很直观地可以想到是由一个或多个子问题“转移”而来,这时我们可以直接使用dp
2.如果一个问题我们难以从“整体”-》“局部”思考,但是可以从“局部”-》“整体”思考,并且找不出反例,这就可以考虑使用贪心,这个思考的过程其实就是我们的贪心策略
3.确定贪心策略后,就去模拟贪心的过程就可以解决问题了,通常是使用“循环”

2.动态规划

2.1动态规划概述

动态规划(Dynamic Programming,DP)是一种算法设计方法,它通过将复杂问题分解为更简单的子问题来解决。这种方法特别适用于那些具有重叠子问题和最优子结构特性的问题。

动态规划的核心在于两个主要概念:状态定义状态转移。状态定义涉及如何描述问题的各个阶段或步骤,而状态转移则涉及如何从一个状态到达另一个状态,通常通过状态转移方程来描述3

2.2动态规划常用场景

  • 最优子结构性质:如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。最优子结构性质为动态规划算法解决问题提供了重要线索。
  • 无后效性:即子问题的解一旦确定,就不再改变,不受在这之后、包含它的更大的问题的求解决策影响(马尔科夫性)。
  • 子问题重叠性质:子问题重叠性质是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只计算一次,然后将其计算结果保存在一个表格中,当再次需要计算已经计算过的子问题时,只是在表格中简单地查看一下结果,从而获得较高的效率,降低了时间复杂度(记忆化)。

在算法题中,dp常常适合于解决以下问题:

  • 最优化问题:如寻找最短路径、最大利润、最小成本等。
  • 计数问题:如计算不同路径的数量或组合的可能性。
  • 决策问题:如资源分配、背包问题、生产计划等,需要在多个可能的选择中找到最优解。
  • 序列问题:如最长公共子序列、最长递增子序列等,涉及到字符串或序列的分析。

2.3动态规划解题模板

对于求最优解的问题、总体问题可以分解为局部问题,我们常常可以使用动态规划求解,对于dp解题步骤,常常有以下步骤:

1.确定dp数组及其下标含义
2.确定递推公式
3.确定递推顺序
4.dp数组如何初始化
5.打印dp数组(debug用)

3.浅谈贪心与动态规划的关系

  • 一般而言,对于二者都可以解决的问题,dp相对于贪心是一种暴力
  • 贪心背后包含的“智力成本”一般大于dp,也就是说贪心以人的“思考”计算机的空间和时间
    • 贪心算法对每个子问题的解决方案都做出选择,不能回退
    • 动态规划则会保存以前的运算结果,并根据以前的结果对当前进行选择,有回退功能。

2.贪心经典题目

区间问题

3.动态规划经典题目

3.1体会“从整体到局部”的思考过程

3.2体会“状态转移方程”系列

(1)力扣309.买卖股票的最佳时期含冷冻期与

原题连接

在做这道题目之前,我曾经做过几道力扣股票系列的其他题目,与其他题目不同的是,这道题目增加了一个冷冻期,使得状态变得十分复杂。

一开始,我只考虑了3个状态

  • 买入状态
  • 卖出状态
  • 冷冻期

这让我只能通过50%的测试用例。究其原因,是上述三个状态会导致状态转移发生混乱(不准确)。
如下图,具体而言,冷冻期其实将卖出状态分为了下面的两个部分,而冷冻期只能由前一个部分(今天卖出状态)转移来,而不能由保持卖出状态转移,如过将两个卖出状态合并,我们定义的状态转移方程将会存在状态转移错误,即冷冻状态也可以由保持卖出状态转移。
在这里插入图片描述
修改为4个状态后,这道题目成功AC了。深入思考和分析这道题目之后,我对“状态转移”四个字的理解更加深刻。代码:

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        //1.确定dp数组及其下标含义
        //dp[i][0]:买入状态
        //dp[i][1]:保持卖出状态
        //dp[i][2]:就在今天卖出
        //dp[i][3]:冷冻期
        //第i天的状态为j,持有的最大现金为dp[j]
        vector<vector<int>> dp(prices.size(), vector<int>(4, 0));


        //4.dp数组如何初始化
        dp[0][0] = - prices[0]; 


        //3.确定递推顺序
        for(int i = 1; i < prices.size(); i++){
            //2.确定递推公式
            //买入状态
            dp[i][0] = max(dp[i -1 ][0], max(dp[i-1][3] - prices[i], dp[i-1][1] -prices[i]));

            //保持卖出状态
            dp[i][1] = max(dp[i - 1][1],dp[i-1][3]);

            //今天卖出
            dp[i][2] = dp[i - 1][0] + prices[i];

            //冷冻状态
            dp[i][3] = dp[i-1][2];

            //5.打印dp数组
            //cout << dp[i][0] << ' ' << dp[i][1] <<' ' << dp[i][2] << ' ' << endl;

        }

        return max(dp[prices.size() - 1][3],max(dp[prices.size() - 1][1], dp[prices.size() - 1][2]));


    }
};

(3)力扣714.买卖股票的最佳时期含手续费

这道题目我是紧随309做的,用自己的思路一次AC,但是有新的体会,我的代码:

class Solution {
public:
    int maxProfit(vector<int>& prices, int fee) {
        //1.确定dp数组及其下标含义
        //dp[i][0]:买入付费状态
        //dp[i][1]:买入保持状态
        //dp[i][2]:卖出状态(不持有股票)
        //第i天的状态为j,最大现金为dp[i][j]
        vector<vector<int>> dp(prices.size(), vector<int>(3,0));
        
        //4.dp数组如何初始化
        dp[0][0] = -prices[0] - fee;
        dp[0][1] = -prices[0] - fee;

        //3.确定递推公式
        for(int i = 1; i < prices.size(); i++){

            //2.确定递推公式
            //买入付费状态
            dp[i][0] = dp[i-1][2]-prices[i] - fee;
            //买入保持状态
            dp[i][1] = max(dp[i-1][0], dp[i-1][1]);
            //卖出状态
            dp[i][2] = max(max(dp[i-1][0] + prices[i], dp[i-1][1] + prices[i]), dp[i-1][2]);
            //cout << dp[i][0] << ' ' << dp[i][1] <<' ' << dp[i][2] << ' ' << endl;

        }

        return dp[prices.size() - 1][2];
     
    }
};

大佬的解法:

class Solution {
public:
    int maxProfit(vector<int>& prices, int fee) {
        int n = prices.size();
        vector<vector<int>> dp(n, vector<int>(2, 0));
        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][0] + prices[i] - fee);
        }
        return max(dp[n - 1][0], dp[n - 1][1]);
    }
};

在这里插入图片描述

可以看到,受到上一道题冷冻期的影响,我把买入(持有)状态分为了“买入付费”和“买入保持”,但其实这两个状态是可以合并为一个状态的,因为这个付手续费行为带来的状态买入付费状态,都是由无操作或者卖出状态转移而来。
因此大佬就没有考虑,直接将我的两个买入状态合并为一个买入状态。

总而言之,定义状态的多少其实不是解体的关键,解题的关键是我们的状态不混乱、能够正确转移。如果将状态更加精细化可以帮助我们理清思路,状态转移正确,这也是可为的。

3.3 子序列问题

(1)

3.4体会什么时候用一维dp数组,什么时候使用多维dp数组

(1)力扣718.最长重复子数组

原题链接

这道题目在开始时我是有些不知道如何下手的,但当我想到只有当数组1的一个元素等于数组2的一个元素时,重复子序列的长度才能加1,

 if(nums1[i] == nums2[j])

我立马就想到最直接的方法是使用二维dp,之后状态转移变得简单起来,完整代码如下:

class Solution {
public:
    int findLength(vector<int>& nums1, vector<int>& nums2) {

        //1.确定dp数组及其下标含义
        //以下标i对应元素结尾的数组1与以下标j对应元素结尾的数组2的最长重复子数组为dp[i][j]
        vector<vector<int> > dp(nums1.size(), vector<int>(nums2.size(), 0));

        //4.dp数组如何初始化
        //dp[1][j] = dp[0][j - 1] + 1 ---->所有dp[0][j]都需要初始化
        //dp[i][1] = dp[i - 1][0] + 1 ---->所有dp[i][0]都需要初始化
        bool isSame = false;
        for(int j = 0; j < nums2.size(); j ++){
            if(nums1[0] == nums2[j]){
                dp[0][j] = 1;
                isSame = true;
            }
        }
        for(int i = 0; i < nums1.size(); i++){
            if(nums1[i] == nums2[0]){
                dp[i][0] = 1;
                isSame = true;
            } 
        }

        //默认两个数组中可以没有相同元素
        int result = 0;
        //经历过初始化后,如果发现有
        if(isSame) result = 1;
        //3.确定递推顺序
        for(int i = 1; i < nums1.size(); i++){
             //2.确定递推公式
            for(int j = 1; j < nums2.size(); j++){
                //如果数组1的当前元素与数组2的当前元素相等,最长长度在上一个状态(数组1以i-1结尾,数组2以j-1结尾)的基础上+1
                //题目隐含的着“子数组”是连续的意思,因此dp[i][j] 只能由dp[i - 1][j - 1]转移而来,而不是dp[i - 1][j]或dp[i][j - 1]
                if(nums1[i] == nums2[j]) dp[i][j] = dp[i - 1][j - 1] + 1; 
                //cout << dp[i][j] << " ";
                ///收集结果
                if(dp[i][j] > result) result = dp[i][j];
            }
            //cout << endl;
        }


       return result;
        
    }
};

3.5体会“遍历顺序”

“遍历顺序”是跟着“动态转移方程来的”,我开始没有这个概念,在做完647. 回文子串,我后知后觉,代码如下:

class Solution {
public:
    int countSubstrings(string s) {

        //1.确定dp数组及其下标含义
        //dp[i][j]表示区间【i,j】是否是回文串,0表示不是,1表示是
        vector<vector<int>> dp(s.size(), vector<int>(s.size(), 0));

        //4.dp数组如何初始化
        
        int result = 0; 
            
        //3.确定遍历顺序
        //根据递推公式,在dp表中,dp[i+1][j-1]在dp[i][j]下方、左方从下到上,从左到右
        for(int i = s.size() - 1; i >=0; i--){
            for(int j = i; j < s.size(); j++){
                //2.确定递推公式
                //dp[i][j]由dp[i+1][j-1]推导出来
                if(s[i] == s[j]){
                    //同一个字符或两个相邻的字符
                    if(j - i <= 1){
                        dp[i][j] = 1;
                        result++;
                    }
                    else{
                        
                        if(dp[i+1][j-1]){
                            dp[i][j] = 1;
                            result++;     
                        }
                    }
                }
                //如果i和j不相等,不更新dp
            }
        }
        return result;


    }
};

体会“背包问题”

树形dp

编辑距离问题

“距离”也可以理解为“操作次数”,这类问题可以进一步具体化为:

  • 两字符串的最大公共子串长度(相对位置不变,不必连续)
  • 两字符串的最长重复子串长度(相对位置不变,需要连续)
  • 字符串1在字符串2中出现的次数(相对位置不变,不必连续)
  • 编辑字符串1和字符串2使它们相同的最小操作次数(只能进行“删除”操作)
  • 编辑字符串1使之成为字符串2的最小操作次数(可以进行“删除”、“增加”、“替换”操作)

4.扩展

优先级队列

大顶堆

参考资料:

  • 维基百科- 贪心
  • 维基百科- 动态规划
  • 代码随想录
  • 洛谷

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

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

相关文章

2024Mac系统热门游戏排行榜 Mac支持的网络游戏有哪些?mac能玩哪些大型网游 苹果电脑Mac游戏资源推荐 Mac玩Windows游戏

“游戏是这个世界上唯一能和女性争夺男朋友的东西&#xff08;/滑稽&#xff0c;有不少女生也喜欢玩游戏&#xff09;。” 虽然只是一句玩笑话&#xff0c;不过也可以看出游戏对大多数男生来说是必不可少的一项娱乐活动了。而网络游戏是游戏中的一大分支&#xff0c;能让玩家们…

zabbix自动发现和自动注册

一、zabbix自动发现 1.1 确保客户端上的zabbix-agent2服务器状态正常 1.2 在web页面删除原有的客户端主机 1.3 在服务端和客户端上配置hosts 1.4 web端配置自动发现 二、zabbix自动注册 2.1 环境配置 2.2 修改zabbix-agent2配置文件 过滤非#或非&#xffe5;开头的内容 2.3 we…

基于遗传优化算法的TSP问题求解matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 基于遗传优化算法的TSP问题求解&#xff0c;分别对四个不同的城市坐标进行路径搜索。 2.测试软件版本以及运行结果展示 MATLAB2022A版本运行 3.核心程序 ....…

前端开发攻略---用原生JS在网页中也能实现语音识别

1、语音识别的过程 语音识别涉及三个过程&#xff1a;首先&#xff0c;需要设备的麦克风接收这段语音&#xff1b;其次&#xff0c;语音识别服务器会根据一系列语法 (基本上&#xff0c;语法是你希望在具体的应用中能够识别出来的词汇) 来检查这段语音&#xff1b;最后&#xf…

基于EBAZ4205矿板的图像处理:02生成测试彩条图像

基于EBAZ4205矿板的图像处理&#xff1a;02生成测试彩条图像 生成测试彩条图像可以有两种方式 VDMA缓存PS端生成彩条图像数据&#xff0c;PL端输出 这里可以直接看超级大电工开源的代码&#xff0c;写的很好详细&#xff0c;我就不再班门弄斧了&#xff08;下面是链接&#…

22 - Hadoop HA 高可用集群搭建、手动模式、自动模式以及HA模式集群

目录 1、HA 概述 2、HDFS-HA 集群搭建 2.1、HDFS-HA 核心问题 3、HDFS-HA 手动模式 3.1、环境准备 3.2、规划集群 3.3、配置 HDFS-HA 集群 3.4、启动 HDFS-HA 集群 4、HDFS-HA 自动模式 4.1、HDFS-HA 自动故障转移工作机制 4.2、HDFS-HA 自动故障转移的集群规划 4.…

AI助力后厨可视化智慧监管,让“舌尖安全”看得见

一、背景与需求分析 夏天是食物易腐败的季节&#xff0c;高温容易引发食品安全问题。在后厨环境中&#xff0c;食品安全问题可能涉及食品加工、后厨环境、食品是否被污染等方面&#xff0c;而不合格的食品安全管理可能导致食品中毒事件等风险&#xff0c;损害消费者的健康和餐…

Asp .Net Core 系列:国际化多语言配置

文章目录 概述术语 本地化器IStringLocalizer在服务类中使用本地化 IStringLocalizerFactoryIHtmlLocalizerIViewLocalizer 资源文件区域性回退 配置 CultureProvider内置的 RequestCultureProvider实现自定义 RequestCultureProvider使用 Json 资源文件 设计原理IStringLocali…

你的动漫AI女友 Anime gf :自定义创建各种独特个性、语言风格的虚拟角色

一个本地且开源的 CharacterAI 替代工具 Anime gf&#xff0c;提供了一个用户友好的界面&#xff0c;允许用户在桌面上与虚拟角色互动。你可以自定义创建各种角色&#xff0c;让每个虚拟角色都有自己的独特个性和语言风格&#xff0c;可以接入OpenAI、Anthropic、Mistral和 Tog…

建立外贸网站常用的WordPress插件

我们最近使用hostease的虚拟主机在创建wordpress外贸网站时&#xff0c;需要选择安装一些插件。对于wordpress建站选择合适的WordPress插件至关重要。面对琳琅满目的插件选择&#xff0c;根据多年的实践经验&#xff0c;我为您推荐以下必备插件清单&#xff0c;让您的网站建设更…

电商红利再现,“视频号小店”即将顶替“抖音小店”

哈喽~我是电商月月 电商行业发展迅速&#xff0c;除了“刚兴起”就入驻的商家&#xff0c;竞争少&#xff0c;市场大&#xff0c;能简简单单吃到第一批红利&#xff0c;后来入驻的商家就需要运用技巧与同行竞争了【要么认真选品&#xff0c;有独特的卖点。要么就是打价格战&am…

系统性文献综述的撰写(Systematic Review)

文献综述 什么是文献综述 对某一个“领域、专业、课题、问题、研究专题”&#xff0c;通过搜集大量的相关资料&#xff08;别人发表的论文&#xff09;&#xff0c;然后通过“阅读、分析、归纳、整理”给出最新进展、学术见解或建议。对其做出综合性介绍和阐述的一种学术论文…

基于SpringBoot和PostGIS的各省与地级市空间距离分析

目录 前言 一、PostGIS时空库 1、时空表设计 2、空间数据管理与查询 二、后台接口设计 1、ORM层设计与实现 2、业务层设计与实现 3、控制层设计 三、web可视化设计与实现 1、省份范围展示 2、城市距离可视化 3、成果展示 总结 前言 在上一篇博客中基于Java和GDAL实…

力扣HOT100 - 78. 子集

解题思路&#xff1a; class Solution {public List<List<Integer>> subsets(int[] nums) {List<List<Integer>> lists new ArrayList<>(); // 解集lists.add(new ArrayList<Integer>()); // 首先将空集加入解集中for(int i 0; i < n…

【nginx】http2 配置造成 多进程请求变成单进程

一、环境简要说明 #访问请求过程 用户&#xff08;浏览器&#xff09; ——> 防火墙映射 ——> nginx ——> app服务&#xff08;java&#xff09; http2是什么&#xff0c;简单来说是继HTTP1.1版本之后的新版HTTP协议&#xff0c;支持二进制分帧、多路复用、首部压缩…

认识Linux及一些基本

目录 linux简介&#xff1a; 1. 发展史 UNIX发展的历史 Linux发展历史 2. 开源 3. 企业应用现状 Linux在服务器领域的发展 Linux在桌面领域的发展 Linux在移动嵌入式领域的发展 Linux在云计算/大数据领域的发展 4. 发行版本 Debian Ubuntu 红帽企业级Linux Cent…

数据结构复习指导之数组和特殊矩阵

文章目录 数组和特殊矩阵 考纲内容 复习提示 前言 1.数组的定义 2.数组的存储结构 3.特殊矩阵的压缩存储 3.1对称矩阵 3.2三角矩阵 3.3三对角矩阵 4.稀疏矩阵 5.知识回顾 数组和特殊矩阵 考纲内容 &#xff08;一&#xff09;栈和队列的基本概念 &#xff08;二&a…

ubuntu neo4j 下载与配置(一)

neo4j 官方下载页面 https://neo4j.com/deployment-center/#community 进入页面之后&#xff0c;往下滑 咱们在下载neo4j时&#xff0c;官方可能要咱们填写一下个人信息&#xff0c;比如&#xff1a;姓名组织结构邮箱等&#xff1a; 咱们可以观察一下&#xff0c;ne4j的下载链…

iOS 实现类似抖音翻页滚动效果

这里是效果图 参考抖音的滚动效果&#xff0c;需要我们在结束拖动的时候&#xff0c;动画设置偏移量 这里有一个注意点&#xff0c;由于我们是在拖动结束的时候&#xff0c;手动改变tableview的偏移量&#xff0c; 改变了tableView 自身原有的的滚动效果&#xff0c;所以我们…

C++奇迹之旅:类和对象const成员static关键字友元内部类

文章目录 &#x1f4dd;const成员&#x1f320; const 成员函数是什么&#xff1f;&#x1f320; 取地址及const取地址操作符重载 &#x1f309;static成员&#x1f320;概念&#x1f320;static特性&#x1f309;static小题 &#x1f320;友元&#x1f309; 友元函数&#x1f…
最新文章