什么是贪心算法
1)最自然智慧的算法
2)用一种局部最功利的标准,总是做出在当前看来是最好的选择
3)难点在于证明局部最功利的标准可以得到全局最优解
4)对于贪心算法的学习主要以增加阅历和经验为主
贪心算法求解的标准过程
分析业务
根据业务逻辑找到不同的贪心策略
对于能举出反例的策略直接跳过,不能举出反例的策略要证明有效性
这往往是特别困难的,要求数学能力很高且不具有统一的技巧性
贪心算法的解题套路
- 实现一个不依靠贪心策略的解法X,可以用最暴力的尝试
- 脑补出贪心策略A、贪心策略B、贪心策略C…
- 用解法X和对数器,用实验的方式得知哪个贪心策略正确
- 不要去纠结贪心策略的证明
第一题
会议安排
一些项目要占用一个会议室宣讲,会议室不能同时容纳两个项目的宣讲。
给你每一个项目开始的时间和结束的时间
你来安排宣讲的日程,要求会议室进行的宣讲的场次最多。
返回最多的宣讲场次。贪心策略:选结束时间早的
package com.lzf2.class11;
import java.util.Arrays;
import java.util.Comparator;
public class BestArrange {
//会议
public static class Program {
public int start;
public int end;
public Program(int start, int end) {
this.start = start;
this.end = end;
}
}
// 会议的开始时间和结束时间,都是数值,不会 < 0
public static int bestArrange2(Program[] programs) {
if(programs == null || programs.length<0){
return 0;
}
//根据结束时间排序
Arrays.sort(programs,new ProgramCompartor());
int curEnd = 0;
int result = 0;
for (Program program : programs) {
if(program.start>=curEnd){
result++;
curEnd = program.end;
}
}
return result;
}
public static class ProgramCompartor implements Comparator<Program> {
@Override
public int compare(Program o1, Program o2) {
return o1.end - o2.end;
}
}
//for test
// 暴力!所有情况都尝试!
public static int bestArrange1(Program[] programs) {
if (programs == null || programs.length == 0) {
return 0;
}
return process(programs, 0, 0);
}
// 还剩下的会议都放在programs里
// done之前已经安排了多少会议的数量
// timeLine目前来到的时间点是什么
// 目前来到timeLine的时间点,已经安排了done多的会议,剩下的会议programs可以自由安排
// 返回能安排的最多会议数量
public static int process(Program[] programs, int done, int timeLine) {
if (programs.length == 0) {
return done;
}
// 还剩下会议
int max = done;
// 当前安排的会议是什么会,每一个都枚举
for (int i = 0; i < programs.length; i++) {
if (programs[i].start >= timeLine) {
Program[] next = copyButExcept(programs, i);
max = Math.max(max, process(next, done + 1, programs[i].end));
}
}
return max;
}
public static Program[] copyButExcept(Program[] programs, int i) {
Program[] ans = new Program[programs.length - 1];
int index = 0;
for (int k = 0; k < programs.length; k++) {
if (k != i) {
ans[index++] = programs[k];
}
}
return ans;
}
public static Program[] generatePrograms(int programSize, int timeMax) {
Program[] ans = new Program[(int) (Math.random() * (programSize + 1))];
for (int i = 0; i < ans.length; i++) {
int r1 = (int) (Math.random() * (timeMax + 1));
int r2 = (int) (Math.random() * (timeMax + 1));
if (r1 == r2) {
ans[i] = new Program(r1, r1 + 1);
} else {
ans[i] = new Program(Math.min(r1, r2), Math.max(r1, r2));
}
}
return ans;
}
public static void main(String[] args) {
int programSize = 12;
int timeMax = 20;
int timeTimes = 1000000;
for (int i = 0; i < timeTimes; i++) {
Program[] programs = generatePrograms(programSize, timeMax);
if (bestArrange1(programs) != bestArrange2(programs)) {
System.out.println("Oops!");
}
}
System.out.println("finish!");
}
}
第二题
金条分割
一块金条切成两半,是需要花费和长度数值一样的铜板的。
比如长度为20的金条,不管怎么切,都要花费20个铜板。 一群人想整分整块金条,怎么分最省铜板?例如,给定数组{10,20,30},代表一共三个人,整块金条长度为60,金条要分成10,20,30三个部分。
如果先把长度60的金条分成10和50,花费60; 再把长度50的金条分成20和30,花费50;一共花费110铜板。
但如果先把长度60的金条分成30和30,花费60;再把长度30金条分成10和20, 花费30;一共花费90铜板。
输入一个数组,返回分割的最小代价。哈夫曼编码就是最优分割
import java.util.PriorityQueue;
public class LessMoneySplitGold {
// 纯暴力!
public static int lessMoney1(int[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}
return process(arr, 0);
}
// 等待合并的数都在arr里,pre之前的合并行为产生了多少总代价
// arr中只剩一个数字的时候,停止合并,返回最小的总代价
public static int process(int[] arr, int pre) {
if (arr.length == 1) {
return pre;
}
int ans = Integer.MAX_VALUE;
for (int i = 0; i < arr.length; i++) {
for (int j = i + 1; j < arr.length; j++) {
ans = Math.min(ans, process(copyAndMergeTwo(arr, i, j), pre + arr[i] + arr[j]));
}
}
return ans;
}
public static int[] copyAndMergeTwo(int[] arr, int i, int j) {
int[] ans = new int[arr.length - 1];
int ansi = 0;
for (int arri = 0; arri < arr.length; arri++) {
if (arri != i && arri != j) {
ans[ansi++] = arr[arri];
}
}
ans[ansi] = arr[i] + arr[j];
return ans;
}
public static int lessMoney2(int[] arr) {
//优先级队列,小根堆
PriorityQueue<Integer> pQ = new PriorityQueue<>();
//结果,最终需要的代价
int sum = 0;
//全部放入小根堆
for (int i = 0; i < arr.length; i++) {
pQ.add(arr[i]);
}
//弹出两个组成一个,在放回小根堆中。
//每次组成的结果累加就是代价
int cur = 0;
while (pQ.size() > 1) {
cur = pQ.poll() + pQ.poll();
pQ.add(cur);
sum += cur;
}
return sum;
}
// for test
public static int[] generateRandomArray(int maxSize, int maxValue) {
int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) (Math.random() * (maxValue + 1));
}
return arr;
}
public static void main(String[] args) {
int testTime = 100000;
int maxSize = 6;
int maxValue = 1000;
for (int i = 0; i < testTime; i++) {
int[] arr = generateRandomArray(maxSize, maxValue);
if (lessMoney1(arr) != lessMoney2(arr)) {
System.out.println("Oops!");
}
}
System.out.println("finish!");
}
}
第三题
做项目获得的最大钱数
输入: 正数数组costs、正数数组profits、正数K、正数M
costs[i]表示i号项目的花费
profits[i]表示i号项目在扣除花费之后还能挣到的钱(利润)
K表示你只能串行的最多做k个项目
M表示你初始的资金
说明: 每做完一个项目,马上获得的收益,可以支持你去做下一个项目。不能并行的做项目。
输出:你最后获得的最大钱数。
package com.lzf2.class11;
import java.util.Comparator;
import java.util.PriorityQueue;
public class IPO {
private static class Program{
int p;
int c;
public Program(int p, int c) {
this.p = p;
this.c = c;
}
}
/**
*
* @param K 最多K个项目
* @param W 初始资金
* @param profits 和capital等长,表示项目的花费
* @param capital 和profits等长,表示项目的存利润
* @return 最终最大的资金
*/
public static int findMaximizedCapital(int K,int W,int[] profits,int[] capital){
PriorityQueue<Program> minCostQ = new PriorityQueue<>(new MinCostComparator());
PriorityQueue<Program> maxProfitQ = new PriorityQueue<>(new MaxProfitComparator());
//1,建出每个项目,放到小根堆
for (int i = 0; i < profits.length; i++) {
minCostQ.add(new Program(profits[i],capital[i]));
}
//2.做 K 个项目
for (int i = 0; i < K; i++) {
while (!minCostQ.isEmpty() && minCostQ.peek().c <= W){
maxProfitQ.add(minCostQ.poll());
}
//没有能做的项目,提前结束
if (maxProfitQ.isEmpty()){
return W;
}
W += maxProfitQ.poll().p;
}
return W;
}
private static class MinCostComparator implements Comparator<Program> {
@Override
public int compare(Program o1, Program o2) {
return o1.c - o2.c;
}
}
private static class MaxProfitComparator implements Comparator<Program> {
@Override
public int compare(Program o1, Program o2) {
return o2.p - o1.p;
}
}
}
第四题
至少要几盏灯
给定一个字符串str,只由‘X’和‘.’两种字符构成。
‘X’表示墙,不能放灯,也不需要点亮
‘.’表示居民点,可以放灯,需要点亮
如果灯放在i位置,可以让i-1,i和i+1三个位置被点亮
返回如果点亮str中所有需要点亮的位置,至少需要几盏灯
i 位置是’X’:不用管直接去 i+1
i 位置是’.’:
1. i + 1 位置是X,i位置必须放灯,跳到i+2位置 2. i + 1 位置是. ,看i + 2是x,i + 1位置放灯跳到 i+ 3 3. i + 1 位置是. ,看i + 2是.,i + 1位置放灯跳到 i+ 3
package com.lzf2.class11;
import java.util.HashSet;
public class Light {
public static int minLight(String road){
if (road == null || road.length() == 0){
return -1;
}
char[] chars = road.toCharArray();
int light = 0;
int index = 0;
while (index < chars.length){
if (chars[index] == 'X'){
index++;
}else {//index 位置是 .
light++;
if (index + 1 == chars.length){
break;
}
if (chars[index + 1] == 'X'){
index+=2;
}else {//index+1 位置是 .
index+=3;
}
}
}
return light;
}
// for test
public static int minLight1(String road) {
if (road == null || road.length() == 0) {
return 0;
}
return process(road.toCharArray(), 0, new HashSet<>());
}
// str[index....]位置,自由选择放灯还是不放灯
// str[0..index-1]位置呢?已经做完决定了,那些放了灯的位置,存在lights里
// 要求选出能照亮所有.的方案,并且在这些有效的方案中,返回最少需要几个灯
public static int process(char[] str, int index, HashSet<Integer> lights) {
if (index == str.length) { // 结束的时候
for (int i = 0; i < str.length; i++) {
if (str[i] != 'X') { // 当前位置是点的话
if (!lights.contains(i - 1) && !lights.contains(i) && !lights.contains(i + 1)) {
return Integer.MAX_VALUE;
}
}
}
return lights.size();
} else { // str还没结束
// i X .
int no = process(str, index + 1, lights);
int yes = Integer.MAX_VALUE;
if (str[index] == '.') {
lights.add(index);
yes = process(str, index + 1, lights);
lights.remove(index);
}
return Math.min(no, yes);
}
}
public static String randomString(int len) {
char[] res = new char[(int) (Math.random() * len) + 1];
for (int i = 0; i < res.length; i++) {
res[i] = Math.random() < 0.5 ? 'X' : '.';
}
return String.valueOf(res);
}
public static void main(String[] args) {
int len = 20;
int testTime = 100000;
for (int i = 0; i < testTime; i++) {
String test = randomString(len);
int ans1 = minLight1(test);
int ans2 = minLight(test);
if (ans1 != ans2) {
System.out.println("oops!");
}
}
System.out.println("finish!");
}
}