1、基本概念
贪心算法是一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,
从而希望导致结果是全局最好或最优的算法。
贪心算法解决问题的策略是:做出选择时,每次都选择对当前状态最优的解,而不考虑整个问题的解空间。它通常用来解决最优化问题,如最小生成树、哈夫曼编码等。
2、步骤
- 建立数学模型来描述问题
- 把求解的问题分成若干个子问题
- 对每个子问题求解,得到子问题的局部最优解
- 把子问题的解局部最优解合成原来解问题的一个解
3、使用条件
利用贪心法求解的问题应具备如下2个特征:
1、贪心选择性质
一个问题的整体最优解可通过一系列局部的最优解的选择达到,并且每次的选择可以依赖以前作出的选择,但不依赖于后面要作出的选择。这就是贪心选择性质。对于一个具体问题,要确定它是否具有贪心选择性质,必须证明每一步所作的贪心选择最终导致问题的整体最优解。
2、最优子结构性质
当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。问题的最优子结构性质是该问题可用贪心法求解的关键所在。在实际应用中,至于什么问题具有什么样的贪心选择性质是不确定的,需要具体问题具体分析。
局部最优解:观察问题是否可以通过选择当前看起来最优的解决方案来逐步构建全局最优解。
问题分解:分析问题是否可以分解成较小的子问题,且这些子问题的最优解能够直接用来构建原问题的最优解。
4、常见的贪心策略类型
- 选择最大(或最小)元素:在解决一些优化问题时,优先选择当前最大或最小的元素
- 资源分配:尽可能地利用可用资源达到最优解,例如,在预算限制下最大化收益。
- 任务调度:在任务规划或调度问题中,基于某种优先级(如最早完成时间、最小持续时间等)来选择任务。
- 集合覆盖:在需要覆盖整个集合的问题中,每一步选择覆盖最多未覆盖元素的选项。
- 动态规划简化:对于某些可以使用动态规划解决的问题,贪心算法提供了更高效的解决方案,尤其是当问题满足某种特定结构时。
5、案列
5.1、分配问题
题目描述: 有一群孩子和一堆饼干,每个孩子有一个饥饿度,每个饼干都有一个大小。每个孩子只能吃最多一个饼干,且只有饼干的大小大于孩子的饥饿度时,这个孩子才能吃饱。求解最多有多少孩子可以吃饱。输入输出样例
输入两个数组,分别代表孩子的饥饿度和饼干的大小。输出最多有多少孩子可以吃饱的数量。Input: [1,2], [1,2,3] Output: 2
题解:
因为饥饿度最小的孩子最容易吃饱,所以我们先考虑这个孩子。为了尽量使得剩下的饼干可以满足饥饿度更大的孩子,所以我们应该把大于等于这个孩子饥饿度的、且大小最小的饼干给这个孩子。满足了这个孩子之后,我们采取同样的策略,考虑剩下孩子里饥饿度最小的孩子,直到没有满足条件的饼干存在。 简而言之,这里的贪心策略是,给剩余孩子里最小饥饿度的孩子分配最1 int findContentChildren(vector<int>& children, vector<int>& cookies) { 2 sort(children.begin(), children.end()); 3 sort(cookies.begin(), cookies.end()); 4 int child = 0, cookie = 0; 5 while (child < children.size() && cookie < cookies.size()) { 6 if (children[child] <= cookies[cookie]) ++child; 7 ++cookie; 8 } 9 return child; 10 }
题目描述:
一群孩子站成一排,每一个孩子有自己的评分。现在需要给这些孩子发糖果,规则是如果一个孩子的评分比自己身旁的一个孩子要高,那么这个孩子就必须得到比身旁孩子更多的糖果;所有孩子至少要有一个糖果。求解最少需要多少个糖果输入输出样例
输入是一个数组,表示孩子的评分。输出是最少糖果的数量。Input: [1,0,2] Output: 5
题解:
虽然这一道题也是运用贪心策略,但我们只需要简单的两次遍历即可:把所有孩子的糖果数初始化为 1;先从左往右遍历一遍,如果右边孩子的评分比左边的高,则右边孩子的糖果数更新为左边孩子的糖果数加 1;再从右往左遍历一遍,如果左边孩子的评分比右边的高,且左边孩子当前的糖果数不大于右边孩子的糖果数,则左边孩子的糖果数更新为右边孩子的糖果数加 1。通过这两次遍历,分配的糖果就可以满足题目要求了。 贪心策略即为,在每次遍历中,只考虑并更新相邻一侧的大小关系。1 int candy(vector<int>& ratings) { 2 int size = ratings.size(); 3 if (size < 2) { 4 return size; 5 } 6 vector<int> num(size, 1); 7 for (int i = 1; i < size; ++i) { 8 if (ratings[i] > ratings[i-1]) { 9 num[i] = num[i-1] + 1; 10 } 11 } 12 for (int i = size - 1; i > 0; --i) { 13 if (ratings[i] < ratings[i-1]) { 14 num[i-1] = max(num[i-1], num[i] + 1); 15 } 16 } 17 return accumulate(num.begin(), num.end(), 0); // std::accumulate 可以很方便地求和 18 }
5.2、区间问题
题目描述
给定多个区间,计算让这些区间互不重叠所需要移除区间的最少个数。起止相连不算重叠。输入输出样例
输入是一个数组,数组由多个长度固定为 2 的数组组成,表示区间的开始和结尾。输出一个整数,表示需要移除的区间数量。Input: [[1,2], [2,4], [1,3]] Output: 1在这个样例中,我们可以移除区间 [1,3],使得剩余的区间 [[1,2], [2,4]] 互不重叠
题解
在选择要保留区间时,区间的结尾十分重要:选择的区间结尾越小,余留给其它区间的空间就越大,就越能保留更多的区间。因此,我们采取的贪心策略为,优先保留结尾小且不相交的区间 具体实现方法为,先把区间按照结尾的大小进行增序排序,每次选择结尾最小且和前一个选择的区间不重叠的区间。我们这里使用 C++ 的 Lambda,结合 std::sort() 函数进行自定义排序。 在样例中,排序后的数组为 [[1,2], [1,3], [2,4]]。按照我们的贪心策略,首先初始化为区间[1,2];由于 [1,3] 与 [1,2] 相交,我们跳过该区间;由于 [2,4] 与 [1,2] 不相交,我们将其保留。因此最终保留的区间为 [[1,2], [2,4]]。1 int eraseOverlapIntervals(vector<vector<int>>& intervals) { 2 if (intervals.empty()) { 3 return 0; 4 } 5 int n = intervals.size(); 6 sort(intervals.begin(), intervals.end(), [](vector<int> a, vector<int> b) { 7 return a[1] < b[1]; 8 }); 9 int total = 0, prev = intervals[0][1]; 10 for (int i = 1; i < n; ++i) { 11 if (intervals[i][0] < prev) { 12 ++total; 13 } else { 14 prev = intervals[i][1]; 15 } 16 } 17 return total; 18 }
5.3、买卖股票
题目描述
给你一个整数数组 prices
,其中 prices[i]
表示某支股票第 i
天的价格。在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。
返回 你能获得的 最大 利润 。
输入输出样例
输入:prices = [7,1,5,3,6,4]
输出:7
解释:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3 。
总利润为 4 + 3 = 7 。
题解
本题采用局部最优的方法,在遍历天数的过程中,时刻确认下一天和当天之间的利润,并且保证相加的时候,利润不可小于0,。其实本题可以抽象把价格数组,直接变成利润数组,转化为数组和这样的题目。因为只要保证当天存在利润,那么往后累加就一定是最大的利润
1 class Solution { 2 public: 3 int maxProfit(vector<int>& prices) { 4 int result = 0; 5 for(int i = 0;i<prices.size()-1;i++){ 6 result += max(prices[i+1]-prices[i],0); 7 } 8 return result; 9 } 10 };
标签:int,孩子,问题,算法,区间,最优,贪心 From: https://www.cnblogs.com/Zhouce/p/18117074