贪心算法
贪心算法(又称贪婪算法),核心思想在求解问题的时候,总是选择当前情况的最优解。贪心算法不是所有问题都能得到整体最优解,核心在于贪心算法的策略选择,选择的贪心策略需要具备无后效性,即某个状态以前的过程不能影响以后的状态至于当前状态有关。
贪心算法总是选择当前情况下的最优选择,而不是从整体最优考虑。贪心算法是做的局部最优选择,最终的结果可能在整体环境下不是最优解,但非常接近最优解。贪心算法也会因为策略不同得到不同的结果。
应用场景
集合覆盖问题
假设存在下面需要付费的广播台,以及广播台信号可以覆盖的地区。 如何选择最少的广播台,让所有的地区都可以接收到信号
实现思路
- 遍历所有电台覆盖的区域,将他装在hashSet中记为allAreas
- 创建临时maxKey指针指向每次循环中包含最多未覆盖地区的电台Key
- 创建一个Selector的List用于记录需要选择的电台
- 遍历每一个电台,将K电台的覆盖地区与allAreas求交集,maxKey指向交集个数最多的一个key(此处体现贪心算法核心,每次只选择最优解)
- 遍历一遍一次得到maxKey加入到Selector到集合中作为需要选择的电台
- maxKey置空 将被覆盖的电台从 allAreas中移除
- 一直循环到allAreas不存在元素 此时Selector中存放的key即是选择方式
代码示例
public class GreedyAlgorithm {
public static void main(String[] args) {
//创建广播电台,放入到Map
HashMap<String, HashSet<String>> broadcasts = new HashMap<String, HashSet<String>>();
//将各个电台放入到broadcasts
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);
// 获取所有的地区集合
Set<String> allAreas = new HashSet<>();
for (HashSet<String> value : broadcasts.values()) {
allAreas.addAll(value);
}
// 定义一个maxKey指针 指向当前覆盖了最多未覆盖地区的Key
String maxKey = null;
// 定义一个临时set用于暂存key中覆盖的电台
Set<String> tempSet = new HashSet<>();
// 定义一个list 用于保存求解的电台结果
List<String> selector = new ArrayList<>();
while (allAreas.size() > 0) {
// 遍历所有的key 确定maxKey的值
for (String key : broadcasts.keySet()) {
tempSet.clear();
tempSet.addAll(broadcasts.get(key));
tempSet.retainAll(allAreas);
// 如果maxKey为空直接指向当前key集合
if (maxKey == null) {
maxKey = key;
} else {
// 如果maxKey不为空 需要判断 maxKey所覆盖的区域 如果比当前key覆盖的区域还要少 一定maxKey指向当前电台
broadcasts.get(maxKey).retainAll(allAreas);
// 当心算法的核心 一直判断当前的交集个数是不是大于 之前maxKey的交集个数 如果是则将maxKey重新赋值
if (tempSet.size() > broadcasts.get(maxKey).size()) {
maxKey = key;
}
}
}
// 确定了maxKey之后从allAreas中移除已经选择了的地区
if (maxKey != null) {
selector.add(maxKey);
allAreas.removeAll(broadcasts.get(maxKey));
// 重置maxKey
maxKey = null;
}
}
System.out.println(selector);
}
}
收益最大化问题 摘自leetcode121
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。
注意:你不能在买入股票前卖出股票。
- 核心就是找出所有股票中的最小值购入,然后从之后的最大值抛出即可得到收益最大化
public class MaxProfit121 {
public static void main(String[] args) {
int[] input = {7, 1, 5, 3, 6, 4};
System.out.println(maxProfit(input));
}
public static int maxProfit(int[] prices) {
if (prices.length == 0) return 0;
int min = prices[0];
int maxProfit = 0;
for (int i = 0; i < prices.length; i++) {
int price = prices[i];
if (min > price) {
// 贪心思想 每次取最小
min = price;
} else {
// 贪心思想 每次取最大
maxProfit = Math.max(price - min, maxProfit);
}
}
return maxProfit;
}
}
收益问题变种摘自leetcode122
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
**注意:**你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
- 与上一个案例区别在于可以多次交易
- 核心思想 对于 [a, b, c, d],如果有 a <= b <= c <= d ,那么最大收益为 d - a。而 d - a = (d - c) + (c - b) + (b - a) ,因此当访问到一个 prices[i] 且 prices[i] - prices[i-1] > 0,那么就把 prices[i] - prices[i-1] 添加到收益中。
public static int maxProfit(int[] prices) {
int maxProfit = 0;
if (prices.length <= 1) return maxProfit;
for (int i = 1; i < prices.length; i++) {
if (prices[i] > prices[i - 1]) maxProfit += (prices[i] - prices[i - 1]);
}
return maxProfit;
}
这道题还有一种可以使用动态规划的解法
-
假定矩阵dp横坐标为第i天的股票 纵坐标为 买或者卖股票的最大收益 0 为卖掉股票 1为买股票
-
7 6 4 3 1 卖0 0 0>6-7=-1=>0 0>4-6 =>0 0>3-4 =>0 0>1-3 =>0 买1 -7 -6 -4 -3 -1 最终结果为0
-
7 1 5 3 6 4 卖0 0 0 > 1-7 => 0 0<5-1=4 => 4 4>3-1 => 4 4<6+1 =>7 7> 4+1 =>7 买1 -7 -1 -1>0-5 => -1 -1 < 4-3=>1 1 > 4-6 =>1 1<7-4=>3 最大收益为7
-
-
买股票的最大收益为 上次交易的最大收益 - 当前股票价格 转成状态方程为 max(dp(i-1,0),dp(i-1,1)-prices[i])
-
买ath.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
public class MaxProfit122ByDynamic {
public static void main(String[] args) {
int[] prices = {7, 6, 4, 3, 1};
System.out.println(maxProfit(prices));
}
public static int maxProfit(int[] prices) {
int len = prices.length;
if (len < 2) {
return 0;
}
// 0 卖股票
// 1 买股票
/** 1 2 3 4 5
* 卖0 0 2-1 3-1
* 买1 -1 -1 -1
* 状态转移方程 收益 int[i][0] = max(int[i-1][0], prices[i] + int[i-1][1]) # 收入减去最低支出 现在支出是负数 所以需要+
* 支出 int[i][1] = max(int[i-1][0],int[i-1][0] - prices[i]) # 上一次的收入减去当前股票的支出
*/
int[][] dp = new int[prices.length][2];
dp[0][0] = 0;
dp[0][1] = -prices[0];
for (int i = 1; i < dp.length; i++) {
dp[i][0] = Math.max(dp[i - 1][0], prices[i] + dp[i - 1][1]);
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
}
return dp[prices.length - 1][0];
}
}