定义
- 贪心算法或贪心思想采用贪心的策略,保证每次操作都是局部最优的,从而使最后得到的结果是全局最优的。(但是不一定是正确解,但是会无限逼近)
集合覆盖问题
- 假设存在如下表的需要付费的广播台,以及广播台信号可以覆盖的地区。 如何选择最少 的广播台,让所有的地区都可以接收到信号
核心思路
- 遍历所有的广播电台, 找到一个覆盖了
最多未覆盖
的地区的电台(此电台可能包含一些已覆盖的地区,但没有关系) - 将这个电台加入到一个集合中(比如
ArrayList
), 想办法把该电台覆盖的地区在下次比较时去掉。 - 重复第1步直到覆盖了全部的地区
注意事项
- 贪心算法所得到的结果不一定是最优的结果(有时候会是最优解),但是都是相对近似(接近)最优解的结果
Java 代码
package Algorithm;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
public class GreedyAlgorithm {
public static void main(String[] args) {
// 创建存储 广播台 与 覆盖地区 Map
HashMap<String, HashSet<String>> broadcasts = new HashMap<String,HashSet<String>>();
HashSet<String> hashSet1 = new HashSet<String>();
hashSet1.add("北京");
hashSet1.add("上海");
hashSet1.add("天津");
HashSet<String> hashSet2 = new HashSet<String>();
hashSet2.add("广州");
hashSet2.add("北京");
hashSet2.add("深圳");
HashSet<String> hashSet3 = new HashSet<String>();
hashSet3.add("成都");
hashSet3.add("上海");
hashSet3.add("杭州");
HashSet<String> hashSet4 = new HashSet<String>();
hashSet4.add("上海");
hashSet4.add("天津");
HashSet<String> hashSet5 = new HashSet<String>();
hashSet5.add("杭州");
hashSet5.add("大连");
//加入到map
broadcasts.put("K1", hashSet1);
broadcasts.put("K2", hashSet2);
broadcasts.put("K3", hashSet3);
broadcasts.put("K4", hashSet4);
broadcasts.put("K5", hashSet5);
HashSet<String> allAreas = new HashSet<String>();
allAreas.addAll(hashSet1) ; // 此方法自动去重
allAreas.addAll(hashSet2) ;
allAreas.addAll(hashSet3) ;
allAreas.addAll(hashSet4) ;
allAreas.addAll(hashSet5) ;
//存放选择的电台集合
ArrayList<String> selects = new ArrayList<String>();
//定义一个临时的集合, 在遍历的过程中,存放遍历过程中的电台覆盖的地区和当前还没有覆盖的地区的交集
// 比如 K1 电台所包含的地区 与 allAreas 的交集
HashSet<String> tempSet = new HashSet<String>();
//定义给maxKey , 保存在一次遍历过程中,能够覆盖最大未覆盖的地区对应的电台的key
//如果maxKey 不为null , 则会加入到 selects
String maxKey = null;
while(allAreas.size() != 0) { // 如果allAreas 不为0, 则表示还没有覆盖到所有的地区
//每进行一次while,需要
maxKey = null;
//遍历 broadcasts, 取出对应key
for(String key : broadcasts.keySet()) {
//每进行一次for
tempSet.clear();
//当前这个key能够覆盖的地区
HashSet<String> areas = broadcasts.get(key);
tempSet.addAll(areas);
//求出tempSet 和 allAreas 集合的交集, 交集会赋给 tempSet
tempSet.retainAll(allAreas);
//如果当前这个集合包含的未覆盖地区的数量,比maxKey指向的集合地区还多
//就需要重置maxKey
// tempSet.size() >broadcasts.get(maxKey).size()) 体现出贪心算法的特点,每次都选择最优的
if(tempSet.size() > 0 &&
(maxKey == null || tempSet.size() >broadcasts.get(maxKey).size())){
maxKey = key;
}
}
//maxKey != null, 就应该将maxKey 加入selects
if(maxKey != null) {
selects.add(maxKey);
//将maxKey指向的广播电台覆盖的地区,从 allAreas 去掉
allAreas.removeAll(broadcasts.get(maxKey));
}
}
System.out.println("得到的选择结果是" + selects);//[K1,K2,K3,K5]
}
}
分配问题
- 有一群孩子和一堆饼干,每个孩子有一个饥饿度,每个饼干都有一个大小。每个孩子只能吃最多一个饼干,且只有饼干的大小大于孩子的饥饿度时,这个孩子才能吃饱。求解最多有多少孩子可以吃饱。🐤🐤
输入: 1 2
1 2 3
输出 : 2
输入输出说明
- 第一行输入为孩子的饥饿度。第二行输入为饼干的分量
核心思路
- 因为饥饿度最小的孩子最容易吃饱,所以我们先考虑这个孩子。为了尽量使得剩下的饼干可以满足饥饿度更大的孩子,所以我们应该把大于等于这个孩子饥饿度的、且大小最小的饼干给这个孩子。满足了这个孩子之后,我们采取同样的策略,考虑剩下孩子里饥饿度最小的孩子,直到没有满足条件的饼干存在。
- 简而言之,这里的贪心策略是,给剩余孩子里最小饥饿度的孩子分配最小的能饱腹的饼干。
- 至于具体实现,因为我们需要获得大小关系,一个便捷的方法就是把孩子和饼干分别排序。
- 这样我们就可以从饥饿度最小的孩子和大小最小的饼干出发,计算有多少个对子可以满足条件。
Java 代码
package Algorithm;
import java.util.Arrays;
import java.util.Scanner;
/**
* @author LZH.create
* 贪心算法之分配饼干问题
*/
public class FindContentChildren {
public static void main(String[] args) {
int [] children = {1,2} ; // 分别表示孩子的饥饿度
int [] cookies = {1,2,3} ;
System.out.println(fun(children,cookies));
}
public static int fun(int [] children ,int [] cookies) {
Arrays.sort(children);
Arrays.sort(cookies);
// 定义两个指针
int child = 0 ;
int cookie = 0 ;
while(child < children.length && cookie < cookies.length) {
if(children[child] < cookies[cookie++]) child++ ;
}
return child ;
}
}
区间问题
-
给定多个区间,计算让这些区间互不重叠所需要移除区间的最少个数。起止相连不算重叠。
-
输入输出样例
输入是一个数组,数组由多个长度固定为2的数组组成,表示区间的开始和结尾。输出一个整数,表示需要移除的区间数量。
Input:[[1,2],[2,4],[1,3]]
Output: 1
在这个样例中,我们可以移除区间[1,3]
便得猁示的区间 [ [1,2] [2,4] ]
互不重叠。
LeetCode 435
跳转链接
核心思路
- 在选择要保留区间时,区间的结尾十分重要:选择的区间结尾越小,余留给其它区间的空间就越大,就越能保留更多的区间。(这里体现贪心策略)
- 因此,我们采取的贪心策略为,优先保留结尾小且不相交的区间。具体实现方法为先把区间按照结尾的大小进行增序排序,每次选择结尾最小且和前一个选择的区间不重叠的区间。
- 在样例中,排序后的数组为
[1,2],[1,3],[2,4].
按照我们的贪心策略,首先初始化为区间[1,2]
,由于[1,3]
与[1,2]
相交,我们跳过该区间;由于[2,4]
与[1,2]
不相交,我们将其保留。因此最终保留的区间为[1,2]
,[2,4]
Java 代码
package Algorithm;
import java.util.Arrays;
import java.util.Comparator;
public class eraseOverlapIntervals {
public static void main(String[] args) {
// TODO Auto-generated method stub
// 测试数据
int [][] arr1 = {{1,2},{2,4},{1,3}} ;
System.out.println(fun(arr1));
int [][] arr2 = {{1,2},{1,2},{1,2}} ;
System.out.println(fun(arr2));
int [][] arr3 = {{1,2},{2,3}} ;
System.out.println(fun(arr3));
}
public static int fun(int [][] arr) {
if(arr.length == 0) {
return 0 ;
}
Arrays.sort(arr ,new Comparator<int []>(){
@Override
public int compare(int [] o1,int [] o2) {
return o1[1]-o2[1] ;
}
});
int count = 1 ;
int pre = arr[0][1] ;
for(int i =1 ; i < arr.length ;i++) {
if(arr[i][0] >= pre) {
count ++ ;
pre = arr[i][1] ;
}
}
return arr.length - count ;
}
}
买股票的最佳时机
核心思路
-
买股票有许多场景
-
关于买股票的类似解法推荐下面大佬的总结 💡
- 首先暴力枚举思路 (时间复杂度肯定高)
动态规划思路
关于动态规划问题大家可以看看本蒟蒻之前写的介绍博文哦 📣
跳转链接
状态定义:
dp[i][j]
:下标为 i 这一天结束的时候,手上持股状态为 j 时,我们持有的现金数。
j = 0
,表示当前不持股;j = 1
,表示当前持股。- 注意:这个状态具有前缀性质,下标为 i 的这一天的计算结果包含了区间
[0, i]
所有的信息,因此最后输出dp[len - 1][0]
。
状态转移方程 (推导):
Case 1 : dp[i][0]
:规定了今天不持股,有以下两种情况:
- 昨天不持股,今天什么都不做;
- 昨天持股,今天卖出股票(现金数增加),
Case 2 : dp[i][1]
:规定了今天持股,有以下两种情况:
- 昨天持股,今天什么都不做(现金数与昨天一样);
- 昨天不持股,今天买入股票(注意:只允许交易一次,因此手上的现金数就是当天的股价的相反数)。
状态方程含义
dp[i][0]
下标为 i 这天结束的时候,不持股,手上拥有的现金数dp[i][1]
下标为 i 这天结束的时候,持股,手上拥有的现金数
Java 代码
package Algorithm;
/**
* @author LZH.create
* Date: 21.4.13
*/
public class BuyStocksProblem_2 {
public static void main(String[] args) {
int [] arr = {7,1,5,3,6,4} ;
System.out.println(fun(arr));
}
public static int fun(int [] prices) {
int len = prices.length ;
if(len < 2) {
return 0 ;
}
int res = 0 ;
for(int i = 1 ; i < len ;i++) {
int sub = prices[i] - prices[i-1] ;
if(sub >0) {
res += sub ;
}
}
return res ;
}
}
贪心算法思路
- 贴个可以使用贪心算法解决的题目
- (因为这里的买股票策略与上面不同可以多次交易)
- 体现贪心的地方 : 由于不限制交易次数,只要今天股价比昨天高,就交易。
Java 代码
package Algorithm;
/**
* @author LZH.create
* Date: 21.4.13
*/
public class BuyStocksProblem_2 {
public static void main(String[] args) {
int [] arr = {7,1,5,3,6,4} ;
System.out.println(fun(arr));
}
public static int fun(int [] prices) {
int len = prices.length ;
if(len < 2) {
return 0 ;
}
int res = 0 ;
for(int i = 1 ; i < len ;i++) {
int sub = prices[i] - prices[i-1] ;
if(sub >0) {
res += sub ;
}
}
return res ;
}
}