题目
一个老板有 n 块金块,他要把最重的一块奖励给最优秀的员工,最轻的一块奖励给次优秀的员工。假设有一台比较重量的仪器,希望用最少的比较次数找出最重和最轻的金块。
分析
- 题意就是在一堆乱序元素中找到两个最值元素:最大值、最小值
- 本题解法思路有两种:分治法、蛮力法
- 分治算法实现上,又可以分两种思路:递归、非递归
- 只看比较次数的话,分治法比较次数稳定,蛮力法比较次数可能最优也可能最差
分治算法(递归)
分:用二分思想将所有金块分成很多小份,每小份金块数量小于或等于2块。
治:金块数量小于或等于2块的集合可以直接比较,找出每一份的最大值和最小值。然后每两份金块相互比较,找出最大值和最小值,以此类推,直到剩下唯一的最大值和最小值,就是最重的金块和最轻的金块。
// 返回数组:[min, max]
public int[] searchMinMax(int[] golds) {
if (golds == null || golds.length == 0) return null;
return search(golds, 0, golds.length - 1);
}
private int[] search(int[] golds, int left, int right) {
if (right - left <= 1) {
int min = Math.min(golds[left], golds[right]);
int max = Math.max(golds[left], golds[right]);
return new int[]{min, max};
} else {
int mid = (left + right) / 2;
int[] part1 = search(golds, left, mid);
int[] part2 = search(golds, mid + 1, right);
part1[0] = Math.min(part1[0], part2[0]);
part1[1] = Math.max(part1[1], part2[1]);
return part1;// 不需要new数组对象,part1和part2是可重复利用的数组
}
}
分治算法(非递归)
非递归算法比递归算法有个优势:数据量增大时,只要JVM堆内存足够,就没有方法栈溢出的调用问题(递归的弊端)。
从算法程序执行的时间顺序上看,递归的过程是“边分边治”,非递归的过程是“先分后治”。
// 返回数组:[min, max]
public int[] searchMinMax(int[] golds) {
if (golds == null || golds.length == 0) return null;
List<int[]> list = divide(golds);// 分
return conquer(list);// 治
}
private List<int[]> divide(int[] golds) {
int length = golds.length;
// list存储每一份金块的最大值和最小值
List<int[]> list = new ArrayList<>((length + 1) / 2);
for (int i = 0; i < length / 2 * 2; i += 2) {// for循环次数,保证偶数次
int min = Math.min(golds[i], golds[i + 1]);
int max = Math.max(golds[i], golds[i + 1]);
list.add(new int[]{min, max});
}
// 金块数量是奇数时,最后一块单独成一份,既是最大值又是最小值
if (length % 2 == 1) list.add(new int[]{golds[length - 1], golds[length - 1]});
return list;
}
private int[] conquer(List<int[]> list) {
while (list.size() > 1) {
int size = list.size();
List<int[]> temp = new ArrayList<>((size + 1) / 2);// 临时temp容量只需要list的一半
for (int i = 0; i < size / 2 * 2; i += 2) {
int[] arr1 = list.get(i);
int[] arr2 = list.get(i + 1);
arr1[0] = Math.min(arr1[0], arr2[0]);
arr1[1] = Math.max(arr1[1], arr2[1]);
temp.add(arr1);// 不再新建数组对象,可重复利用arr1或者arr2数组
}
if (size % 2 == 1) temp.add(list.get(size - 1));// 奇数时的最后一份
list = temp;
}
return list.get(0);
}
蛮力算法
随便拿出一个金块,把它当作最重金块,同时也把它当作最轻金块。然后遍历剩下的金块集合,如果有比最重金块还重的,就替换最重金块,如果有比最轻金块还轻的,就替换最轻金块。遍历完以后,就得到最重金块和最小金块。
// 返回数组:[min, max]
public int[] searchMinMax(int[] golds) {
if (golds == null || golds.length == 0) return null;
int min = golds[0], max = golds[0];
for (int i = 1; i < golds.length; i++) {
if (golds[i] < min) min = golds[i];
else if (golds[i] > max) max = golds[i];
}
return new int[]{min, max};
}