局部最小问题(使用二分法)

复制代码
/**
 * 局部最小问题
 * 已知一个数组arr,该数组无序,且相邻两个数不相等。
 * 求:得出一个局部最小的值的索引。(返回一个即可)
 * <p>
 * 现约定:
 * 如果第一个数比第二个数小,则第一个数称为局部最小。
 * 如果最后一个数比倒数第二个数小,则最后一个数称为局部最小。
 * 对于数组中间的数,如果它小于左边相邻的数,且小于右边相邻的数,则称这个数为局部最小。
 * <p>
 * 引申:使用二分法的前提,必须是数组有序吗?
 * 答:不是必须。局部最小问题,就是反例。
 */
public class Code04_BSAwesome {
    public static int oneMinIndex(int[] arr) {
        int N = arr.length;
        if (arr == null || N == 0) {
            return -1;
        }
        if (N == 1) {
            return 0;
        }
        //下边这两个if,描述的是数组两端的数,比较的情况。
        if (arr[0] < arr[1]) {
            return 0;
        }
        if (arr[N - 2] > arr[N - 1]) {
            return N - 1;
        }
        int L = 0;
        int R = N - 1;
        //L <= R - 2,和L < R - 1,这两个条件等价,表述的都是,当前循环中,数组的长度,大于等于3。
        //方法中使用R=mid-1和L=mid+1的前提是:mid-1和mid+1,都大于等于L,且都小于等于R。

        //如果while中的条件写成(L < R),并且测试样本很大时,则一定会出现数组下标越界的情况。
        //且越界的位置是,标记行①中的arr[mid - 1]。因为此时mid = 0,所以越界的index为-1.

        //举例1:[3, 2, 3, 2, 3]。首先mid来到索引2位置,arr[mid]即3,不小于arr[1](即2),也不小于arr[3](即2)。
        //所以L=mid-1,L=2-1,L=1。来到下一个循环,此时L=0,则mid=(0+1)/2,则mid=0。
        //此时来到标记①这一行,因为mid=0,所以arr[mid-1]越界,越界的索引为-1。

        //举例2:[89, 5, 42, 12, 89, 95],报错的情况同上。
        while (L <= R - 2) {
//        while (L < R - 1) {
            int mid = L + ((R - L) >> 1);
            //如果一个数既比左边相邻的数小,又比右边相邻的数小。则这个数称为局部最小。
            if (arr[mid] < arr[mid - 1] && arr[mid] < arr[mid + 1]) {//标记行①
                return mid;
            } else {//否则
                if (arr[mid] > arr[mid - 1]) {//这个数大于左边相邻的数。
                    R = mid - 1;//即,砍掉mid(含)的右边。看左边,去左边去找,局部最小的值的索引。
                } else {//else的情况就是(arr[mid] < arr[mid] - 1)。因为相邻两个数不相等。
                    L = mid + 1;//即,砍掉mid(含)的左边。看右边,去右边去找,局部最小的值的索引。
                }
            }
        }
        //如果此时来到while循环外边,那么就只剩2个数了,比较一下,谁小谁就是局部最小。
        return arr[L] < arr[R] ? L : R;
    }

    //随机生成一个数组,相邻数不相等。
    public static int[] randomArr(int maxLen, int maxValue) {
        int len = (int) (Math.random() * maxLen);
        int[] arr = new int[len];
        if (len > 0) {
            //如果len>0,先随机生成第一个数。
            arr[0] = (int) (Math.random() * maxValue);
            for (int i = 1; i < len; i++) {
                do {
                    arr[i] = (int) (Math.random() * maxValue);
                } while (arr[i] == arr[i - 1]);//如果当前随机生成的数,和前一个数相等,就重新生成随机数。
            }
        }
        return arr;
    }

    //用于测试,给一个数组arr,和局部最小值的索引minIndex。
    //返回minIndex是否是arr中的局部最小值的索引。
    /**
     * @param arr      一个数组,相邻数不相等
     * @param minIndex 局部最小值的索引
     * @return minIndex是否真的是局部最小值的索引
     */
    public static boolean check(int[] arr, int minIndex) {
        if (arr.length == 0) {
            return minIndex == -1;
        }
        int left = minIndex - 1;
        int right = minIndex + 1;
        //如果局部最小值左边的数的索引left,大于等于0,那么就让arr[left]和arr[minIndex]真去比一下;
        //否则,返回true,即,局部最小值左边的数 > 局部最小值。
        boolean leftBigger = left >= 0 ? arr[left] > arr[minIndex] : true;
//        boolean leftBigger = left < 0 || arr[left] > arr[minIndex];//简化后
        //如果局部最小值右边的数的索引right,小于数组arr的长度(最大也是arr.length - 1,即没有越界),那么就让arr[right]和arr[minIndex]真去比一下;
        //否则,返回true,即,局部最小值右边的数 > 局部最小值。
        boolean rightBigger = right < arr.length ? arr[right] > arr[minIndex] : true;
//        boolean rightBigger = right >= arr.length || arr[right] > arr[minIndex];简化后
        //如果局部最小值,它的左边大于它,并且右边也大于它。那么它,确实是局部最小。
        return leftBigger && rightBigger;
    }

    public static void printArr(int[] arr) {
        for (int value : arr) {
            System.out.print(value + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        int testTimes = 10000;
        int maxLen = 20;
        int maxValue = 100;
        boolean flag = true;
        System.out.println("测试开始!");
        for (int i = 0; i < testTimes; i++) {
            int[] arr = randomArr(maxLen, maxValue);
            int minIndex = oneMinIndex(arr);
            if (!check(arr, minIndex)) {
                flag = false;
                printArr(arr);
                System.out.println("minIndex = " + minIndex);
                break;
            }
        }
        System.out.println("flag = " + flag);
        System.out.println("测试结束!");
    }

    //测试数据
    int[] arr1 = {71, 21, 91, 50, 38, 65, 87, /*diff*/90, 18, 65, 5, 57, 91, 16, 88};
    //minIndex=8,arr[minIndex] = 18
    int[] arr2 = {71, 21, 91, 50, 38, 65, 87, /*diff*/20, 18, 65, 5, 57, 91, 16, 88};
    //minIndex=4,arr[minIndex] = 38
}
复制代码

 

posted @   TheFloorIsNotTooHot  阅读(113)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
点击右上角即可分享
微信分享提示