数据结构与算法(六) 贪心算法
创始人
2024-03-15 14:11:48
0

这篇文章来讲贪心算法(Greedy Algorithm),这是一种特殊的动态规划算法


1、本质

我们在之前的文章中讲过,动态规划可以解决一类具有最优子结构和重叠子问题特征的问题

贪心算法本质上是一种特殊的动态规划算法,因此在看这篇文章前,建议先看之前动态规划的文章


回到正题,先来简单回顾下动态规划,动态规划的核心就是状态转移方程

无论是自上而下利用递归的解题思路,还是自下而上利用递推的解题思路,都可以用状态转移方程串联起来

状态转移方程的关键就是怎么根据较小规模的问题推导出较大规模的问题

在每一步推导的过程中,其实就是在做一件事情,即在当前状态下,尝试所有选择,到达新的状态

然后不断重复这个过程,从已知状态开始直至待求状态结束


跳跃游戏 II | leetcode45

给定一个非负整数数组,初始位于数组的第一个下标,数组中的元素代表所在位置可以跳跃的最大长度

假设一定可以到达数组最后一个位置,问最少需要跳多少次才能到达

为了更好地理解这一过程,这里以跳跃游戏为例,假设给定数组 [2, 3, 1, 1, 4],画出递归树如下:

在这里插入图片描述

注意到这个问题是满足最优子结构和重叠子问题的,因此必定可以用动态规划来解

这里以自上而下的解题思路为例给出代码

class Solution {
public:vector memo;// 动态规划int dp(vector& nums, int pos) {int n = nums.size();// 边界条件if (pos >= n - 1) {return 0;}// 查备忘录if (memo[pos] != n) {return memo[pos];}// 尝试所有选择,优化点// 写备忘录for (int i = 1; i <= nums[pos]; i++) {memo[pos] = min( memo[pos], dp(nums, pos + i) + 1 );}// 返回return memo[pos];}int jump(vector& nums) {int n = nums.size();memo = vector(n, n); // 初始化备忘录return dp(nums, 0);}
};

动态规划在每一步的推导过程中,都要尝试所有选择,这是因为在某些问题中无法判断哪个选择是最优选择

但是贪心算法无需尝试所有选择,而是可以通过推导得出当前最优解,并且局部最优解可以到达全局最优解

假设现在站在索引 0 的位置上,其可跳长度为 2,那么可能的选择有:跳到索引 1 的位置上、跳到索引 2 的位置上。动态规划的思路是,我不知道这两种做法哪种好,所以我就两种都试一下。而贪心算法建立在更强的约束上,在面临多个选择时,能判断出当前最优解是什么。回到上述例子,索引 1 位置的可跳长度是 3,如果当前选择跳到索引 1,那么下一步最远可以跳到索引 4;同理,索引 2 位置的可跳长度是 1,如果当前选择跳到索引 2,那么下一步最远可以跳到索引 3。明显当前跳到索引 1 是优于跳到索引 2 的,因为下一步可跳位置的选择更多,因此贪心算法就会直接选择跳到索引 1,而不会再去尝试跳到索引 2

class Solution {
public:// 无需递归int jump(vector& nums) {int n = nums.size();int pos = 0; // 当前位置int ans = 0; // 当前步数// 进主流程while (pos < n - 1) {int nxtPos = 0; // 下一位置int maxPos = 0; // 最远位置// 直接判断最优位置// 无需尝试所有选择for (int i = 0; i <= nums[pos]; i++) {if (pos + i >= n - 1) {nxtPos = pos + i;break;}if (maxPos < pos + i + nums[pos + i]) {maxPos = pos + i + nums[pos + i];nxtPos = pos + i;}}pos = nxtPos;ans = ans + 1;}// 返回return ans;}
};

2、核心

贪心算法是一种特殊的动态规划算法,它的约束更强,适用场景更少,但一般来说具有更高的效率

这是因为动态规划需要尝试所有选择,而贪心算法则可以通过推导直接选择当前最优解


贪心算法所解决的问题除了要满足最优子结构外,更重要的是要满足贪心选择性质

所谓的贪心选择性质就是说每次选择仅依赖于之前的选择,而不依赖于后续的选择

理解这个性质很关键,这是贪心算法的核心

在每一步推导过程中,贪心算法在当前状态下做局部最优解以到达下一状态,直至到达待求的问题

对于一个具体的问题,使用贪心算法前必须要证明每一步推导所做的局部最优解能到达全局最优解


凑零钱问题 | leetcode322

给定一个整数数组 coins 表示不同面额的硬币,以及一个整数 amount 表示总金额

假设每种硬币的数量是无限的,问凑成总金额所需的最少的硬币个数,若无法凑成,则返回 -1

注意,如果盲目使用贪心算法,可能取不到全局最优解,这里以凑零钱问题为例说明这种情况

对于上述问题,使用贪心算法的一个直观思路是:每次选择面额最大的硬币,直至凑满总金额

这对于普通的面额分布确实是可行的,例如 coins = [1, 5, 10], amount = 13

但对于特殊的面额分布就会出现问题,例如 coins = [1, 4, 5 ], amount = 13

如果使用贪心算法,那么选择的硬币依次是 5、5、1、1、1,但最优选择方式却是 5、4、4


3、框架

贪心算法的框架很简单,总结起来就是一句话:在推导过程中,每次都选择当前最优解

只不过在使用贪心算法时需要特别注意,局部最优解必须能到达全局最优解


4、例题

(1)无重叠区间 | leetcode435

给定一个区间集合,返回移除区间的最小数量,使得剩余的区间互不重叠

基本思路

  1. 题目要求移除最少数量的区间使得剩余的区间互不重叠

    可转换为选择最多数量的区间使得选择的区间互不重叠

  2. 根据贪心算法的思路,每次选择右端点最小且不与先前区间重叠的区间即可

    这是因为同样是增加一个区间,如果选择右端点较小的区间,那么以后选择的可能性更多

class Solution {
public:int eraseOverlapIntervals(vector>& intervals) {// 特判int n = intervals.size();if (n <= 0) {return 0;}// 预处理,为了提高效率// 按右端点从小到大排序sort(intervals.begin(), intervals.end(), [](const auto& u, const auto& v) {return u[1] < v[1];});// 每次选择右端点最小且不与先前区间重叠的区间int ans = 1;int end = intervals[0][1];for (int i = 1; i < n; i++) {// 因为数组已提前排序,所以按顺序遍历就能选择右端点最小的区间// 然后只需保证当前区间与先前区间无重叠,也即当前区间的左端点大于等于先前区间的右端点if (intervals[i][0] >= end) {ans = ans + 1;end = intervals[i][1];}}// 返回结果return n - ans;}
};

(2)单调递增的数字 | leetcode738

给定一个整数,返回小于等于这个数字的最大数字,使得返回的数字是单调递增的

基本思路

  1. 从折线图的角度来看,从左往后找第一个谷顶,将这个数字减去一,将后面数字改为九

    如:13542 -> 13499

  2. 若第一个谷顶是平的,则找谷顶最左边的位置,将这个数字减去一,将后面数字改为九

    如:15542 -> 14999

class Solution {
public:int monotoneIncreasingDigits(int n) {if (n < 10) {return n;}string s = std::to_string(n);int m = s.size();int i = 0;while (i < m - 1 && s[i] <= s[i + 1]) {i++;}if (i == m - 1) {return n;}while (i > 0 && s[i] == s[i - 1]) {i--;}s[i] = s[i] - 1;i++;while (i < m) {s[i] = '9';i++;}return stoi(s);}
};

相关内容

热门资讯

AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWR报告解读 WORKLOAD REPOSITORY PDB report (PDB snapshots) AW...
AWS管理控制台菜单和权限 要在AWS管理控制台中创建菜单和权限,您可以使用AWS Identity and Access Ma...
​ToDesk 远程工具安装及... 目录 前言 ToDesk 优势 ToDesk 下载安装 ToDesk 功能展示 文件传输 设备链接 ...
群晖外网访问终极解决方法:IP... 写在前面的话 受够了群晖的quickconnet的小水管了,急需一个新的解决方法&#x...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
Azure构建流程(Power... 这可能是由于配置错误导致的问题。请检查构建流程任务中的“发布构建制品”步骤,确保正确配置了“Arti...