贪心算法
- 最自然智慧的算法
- 用一种局部最功利的标准,总是做出在当前看来是最好的选择
- 难点在于证明局部最功利的标准可以得到全局最优解
- 对于贪心算法的学习主要以增加阅历和经验为主
1. 组成最小字典序
给定一个由字符串组成的数组strs, 必须把所有的字符串拼接起来,返回所有可能的拼接结果中,字典序最小的结果
需要证明这两个点。太难了,证明麻烦
- a b c 表示 3 个字符串,a + b 表示 a 拼接上 b
若 a + b <= b + a, b + c <= c + b 证明:a + c <= c + a - 数据按照 1 的方式进行排序后得到的字符串是最小的字符串,交换任意两个位置,都会导致字典序变大
public String minDictStr(List<String> strs) {
List<String> strs2 = strs.stream()
.filter(it -> !"".equals(it))
.sorted((a, b) -> (a + b).compareTo(b + a))
.collect(Collectors.toList());
return String.join("", strs2);
}
public String minDictStrCompare(List<String> strs) {
if (strs.size() == 0) {
return "";
}
Stack<String> vals = new Stack<>();
List<String> res = new ArrayList<>();
process(strs, vals, res);
res.sort(String::compareTo);
return res.get(0);
}
private void process(List<String> strs, Stack<String> vals, List<String> res) {
if (strs.size() == 0) {
res.add(String.join("", vals));
return;
}
for (int i = 0; i < strs.size(); i++) {
String str = strs.get(i);
strs.remove(i);
vals.add(str);
process(strs, vals, res);
strs.add(i, str);
vals.pop();
}
}
@Test
public void test1() {
for (int i = 0; i < 10000; i++) {
List<String> strs = Reduce.stringList(8, 10);
List<String> strs1 = new ArrayList<>(strs);
String r1 = minDictStr(strs);
String r2 = minDictStrCompare(strs);
if (!r1.equals(r2)) {
System.out.println(strs);
System.out.println(r1);
System.out.println(r2);
r1 = minDictStr(strs1);
r2 = minDictStrCompare(strs1);
return;
}
}
}
2. 安排最多会议
给一个二维数组,每个元素 [s, end] 表示会议的开始、结束时间,单位是秒,从 0 - 86400,一个会议室,一次只能安排一个会议,问:一天内,最多安排多少个会议。
public int maxMeetNum(int[][] arr) {
int res = 0;
// 先按照结束时间进行排序
Arrays.sort(arr, Comparator.comparingInt(a -> a[1]));
// 当前时间点
int t = 0;
for (int[] se : arr) {
// 开始时间小于当前时间可以安排会议,如果大于,不能安排
if (t <= se[0]) {
// 会议数加1
res++;
// 当前时间来到会议结束时间
t = se[1];
}
}
return res;
}
public int maxMeetNumCompare(int[][] arr) {
return processMeetNum(arr, new int[arr.length], 0);
}
private int processMeetNum(int[][] arr, int[] decide, int i) {
if (i == arr.length) {
List<int[]> arr2 = new ArrayList<>();
for (int j = 0; j < decide.length; j++) {
if (decide[j] == 1) {
arr2.add(arr[j]);
}
}
arr2.sort(Comparator.comparingInt(a -> a[1]));
int t = 0;
for (int[] ints : arr2) {
if (t <= ints[0]) {
t = ints[1];
} else {
return 0;
}
}
return arr2.size();
}
decide[i] = 0;
int max = processMeetNum(arr, decide, i + 1);
decide[i] = 1;
max = Math.max(max, processMeetNum(arr, decide, i + 1));
decide[i] = 0;
return max;
}
@Test
public void test2() {
for (int i = 0; i < 10000; i++) {
int[][] arr = Reduce.lineArray(10, 0, 10);
int r1 = maxMeetNum(arr);
int r2 = maxMeetNumCompare(arr);
if (r1 != r2) {
System.out.println(Printer.print(arr));
System.out.println(r1);
System.out.println(r2);
return;
}
}
}
3. 切分金条(哈弗汉编码问题)
一块金条切成两半,是需要花费和长度数值一样的铜板的。
比如长度为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铜板。输入一个数组,返回分割的最小代价。
public int minMoney(int[] arr) {
if (arr.length == 0) {
return 0;
}
if (arr.length == 1) {
return arr[0];
}
PriorityQueue<Integer> queue = new PriorityQueue<>();
for (int len : arr) {
queue.offer(len);
}
int money = 0, v;
while (queue.size() > 1) {
// 每次取出最上面的两个进行合并
v = queue.poll() + queue.poll();
money += v;
queue.offer(v);
}
return money;
}
public int minMoneyCompare(int[] arr) {
return processMinMoney(arr, 0);
}
private int processMinMoney(int[] arr, int pre) {
if (arr.length == 0) {
return 0;
}
if (arr.length == 1) {
return pre == 0 ? arr[0] : pre;
}
int min = Integer.MAX_VALUE;
for (int i = 0; i < arr.length - 1; i++) {
for (int j = i + 1; j < arr.length; j++) {
min = Math.min(min, processMinMoney(mergeTwo(arr, i, j), pre + arr[i] + arr[j]));
}
}
return min;
}
private int[] mergeTwo(int[] arr, int i, int j) {
int[] arr2 = new int[arr.length - 1];
int m = arr[i] + arr[j], idx = 0;
for (int k = 0; k < arr.length; k++) {
if (k != i && k != j) {
arr2[idx++] = arr[k];
}
}
arr2[idx] = m;
return arr2;
}
@Test
public void test3() {
for (int i = 0; i < 1000; i++) {
int[] arr = Reduce.array(8, 1, 10);
int r1 = minMoney(arr);
int r2 = minMoneyCompare(arr);
if (r1 != r2) {
System.out.println(Printer.print(arr));
System.out.println(r1);
System.out.println(r2);
return;
}
}
}
4. 最大收益数
输入: 正数数组 costs、正数数组 profits、正数K、正数M,costs[i]表示i号项目的花费
profits[i] 表示 i 号项目在扣除花费之后还能挣到的钱(利润),
K 表示你只能串行的最多做 k 个项目
M表示你初始的资金
说明:每做完一个项目,马上获得的收益,可以支持你去做下一个项目。不能并行的做项目。
输出︰你最后获得的最大钱数。
- 选择所有我可以做的项目
- 从我可以做的项目中选择收益最高的一个项目,资金 = 已有资金 + 收益
public int maxBenefit(int[] cost, int[] profits, int K, int M) {
PriorityQueue<int[]> projects = new PriorityQueue<>(Comparator.comparingInt(a -> a[0]));
PriorityQueue<int[]> benefits = new PriorityQueue<>((a, b) -> b[1] - a[1]);
for (int i = 0; i < cost.length; i++) {
int[] v = new int[]{cost[i], profits[i]};
projects.offer(v);
}
for (int i = 0; i < K; i++) {
while (!projects.isEmpty() && projects.peek()[0] <= M) {
benefits.add(projects.poll());
}
if (benefits.isEmpty()) {
return M;
}
M += benefits.poll()[1];
}
return M;
}
5. 放灯问题
给定一个字符串str,只由 ‘X’ 和 ‘:’ 两种字符构成。
'X’ 表示墙,不能放灯,也不需要点亮
∵’ 表示居民点,可以放灯,需要点亮
如果灯放在i位置,可以让i-1,i和i+1三个位置被点亮返回如果点亮str中所有需要点亮的位置,至少需要几盏灯
/**
* 给定一个字符串str,只由 ‘X’ 和 ‘:’ 两种字符构成。
* 'X’ 表示墙,不能放灯,也不需要点亮
* ∵’ 表示居民点,可以放灯,需要点亮
* 如果灯放在i位置,可以让i-1,i和i+1三个位置被点亮返回如果点亮str中所有需要点亮的位置,至少需要几盏灯
*/
public int minLight(char[] chars) {
int light = 0;
int i = 0;
while (i < chars.length) {
if (chars[i] == 'X') {
i++;
} else {
light++;
if (i + 1 == chars.length) {
return light;
} else if (chars[i + 1] == '.') {
i = i + 3;
} else {
i = i + 2;
}
}
}
return light;
}
public int minLightCompare(char[] chars) {
return processMinLight(chars, 0, 0, 0);
}
private int processMinLight(char[] chars, int p, int pre, int f) {
if (p == chars.length - 1 && f == 0 && chars[p] == '.') {
return pre + 1;
}
if (p >= chars.length) {
return pre;
}
if (chars[p] == 'X') {
if (f == 0) {
return processMinLight(chars, p + 1, pre, 0);
}
return processMinLight(chars, p + 1, pre + 1, 0);
}
int min = processMinLight(chars, p + 2, pre + 1, 0);
if (f == 1) {
min = Math.min(min, processMinLight(chars, p + 2, pre + 1, 0));
} else {
min = Math.min(min, processMinLight(chars, p + 1, pre, 1));
}
return min;
}
@Test
public void test5() {
char[] arr1 = new char[]{'X', '.'};
for (int i = 0; i < 10000; i++) {
char[] arr = Reduce.chars(10, arr1);
int r1 = minLight(arr);
int r2 = minLightCompare(arr);
if (r1 != r2) {
System.out.println(new String(arr));
System.out.println(r1);
System.out.println(r2);
r1 = minLight(arr);
r2 = minLightCompare(arr);
return;
}
}
}