[分治法、蛮力法] 金块问题

题目

一个老板有 n 块金块,他要把最重的一块奖励给最优秀的员工,最轻的一块奖励给次优秀的员工。假设有一台比较重量的仪器,希望用最少的比较次数找出最重和最轻的金块。

分析

  1. 题意就是在一堆乱序元素中找到两个最值元素:最大值、最小值
  2. 本题解法思路有两种:分治法、蛮力法
  3. 分治算法实现上,又可以分两种思路:递归、非递归
  4. 只看比较次数的话,分治法比较次数稳定,蛮力法比较次数可能最优也可能最差

分治算法(递归)

分:用二分思想将所有金块分成很多小份,每小份金块数量小于或等于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 list = divide(golds);// 分
    return conquer(list);// 治
}

private List divide(int[] golds) {
    int length = golds.length;
    // list存储每一份金块的最大值和最小值
    List 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 list) {
    while (list.size() > 1) {
        int size = list.size();
        List 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};
}

 

你可能感兴趣的:(算法,金块问题,Java,分治算法,蛮力算法,递归)