primary 03-01: 二分法及进阶问题

本文介绍了如何运用二分法在不同场景下解决查找问题,包括在有序数组中找到大于等于目标值的最左位置和小于等于目标值的最右位置。同时,详细探讨了在无序数组中寻找局部最小值的问题,通过二分法实现高效解决方案,并提供了代码实现和测试案例。二分法不仅限于有序数组,只要有合适的判断标准,就能在满足条件的数组中有效搜索目标值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

二分法

先说一个东西,确定左右边界的时候,有个技巧目前一直遵守:
就是

if () {
	L = mid + 1;
}else(){
	R = mid - 1;
}

大概意思就是,L与R,一个是 (mid + 1) 时,如果else/else if/if条件中是确定另一个边界,则另一边界值为 (mid - 1)–> 可直接写。

  1. 基本二分法 (加入对数器验证)
import java.util.Arrays;

public class BinarySearch {
    public static boolean binarySearch(int[] arr, int target) {
        if (arr == null || arr.length == 0) {
            return false;
        }
        int L = 0;
        int R = arr.length - 1;
        while (L <= R) {
            int mid = (L + R) / 2;
            if (arr[mid] == target) {
                return true;
            } else if (arr[mid] < target) {
                L = mid + 1;
            } else {
                R = mid - 1;
            }
        }
        return false;
    }

    // 对数器
    public static boolean test(int[] arr, int target) {
        for (int num : arr) {
            if (num == target) {
                return true;
            }
        }
        return false;
    }

    // 随机数组生成器
    public static int[] lenRandomValueRandom(int maxLen, int maxValue) {
        int len = (int) (Math.random() * maxLen); // int [0, maxLen - 1]
        int[] arr = new int[len];
        for (int i = 0; i < len; i++) {
            arr[i] = (int) (Math.random() * maxValue); // int [0, maxValue - 1]
        }
        return arr;
    }

    public static int[] copyArray(int[] oldArr) {
        int[] newArr = new int[oldArr.length];
        for (int i = 0; i < oldArr.length; i++) {
            newArr[i] = oldArr[i];
        }
        return newArr;
    }

    public static void main(String[] args) {
        int maxLen = 50;
        int maxValue = 1000;
        int testTimes = 10000;
        // 开始实测
        for (int i = 0; i < testTimes; i++) {
            int[] arr = lenRandomValueRandom(maxLen, maxValue);
            // 二分法检测 一定要保证 !!!数组已经排序!!!
            Arrays.sort(arr);
            int[] tmp = copyArray(arr);
            int target = (int) (Math.random() * (maxValue + 1)) - (int) (Math.random() * maxValue);
            if (binarySearch(arr, target) != test(arr, target)) {
                System.out.println("结果有误");
                for (int j = 0; j < tmp.length; j++) {
                    System.out.print(tmp[j] + " ");
                }
                System.out.println();
                System.out.println("搜索数字为" + target);
                break;
            };

        }
    }
}

二分进阶

  1. 在有序数组中找到 >= num 最左的位置
  2. 在有序数组中找到 <= num 最右的位置
package class03;

import java.util.Arrays;

public class BSNearLeft {
    // arr是有序的, 寻找 >= num 的最左位置
    public static int bsNearLeft(int[] arr, int num) {
        if (arr == null || arr.length == 0) {
            // -1表示该数组无法返回所求条件 --> -1代表特殊情况
            return -1;
        }
        int L = 0;
        int R = arr.length - 1;
        // Step 1: 初始化 存储的目标值 ans --> -1 表示不存在
        int ans = -1;
        while (L <= R) {
            // Step 2: 更新 ans
            int mid = (L + R) / 2;
            if (arr[mid] >= num) {
                ans = mid;
                R = mid - 1;
            } else { // arr[mid] < num
                L = mid + 1;
            }
        }
        // Step 3: 返回 ans
        return ans;
    }

    // 返回 <= num 的最右, 前提:数组有序
    public static int bsNearRight(int[] arr, int num) {
        if (arr == null || arr.length == 0) {
            return -1;
        }
        int L = 0;
        int R = arr.length - 1;
        int ans = -1;
        while (L <= R) {
            int mid = (L + R) / 2;
            if (arr[mid] <= num) {
                ans = mid;
                L = mid + 1;
            } else {
                R = mid - 1;
            }
        }
        return ans;
    }

    // 对数器 --> 确认结果的检查器  功能:检查 >= num 的最左
    // 前提: arr是有序的
    public static int checkNearLeft(int[] arr, int num) {
        if (arr.length == 0) {
            return -1;
        }
        int ans = -1;
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] >= num) {
                ans = i;
                break;
            }
        }
        return ans;
    }

    // 检查 <= num 的最右
    public static int checkNearRight(int[] arr, int num) {
        if (arr.length == 0) {
            return -1;
        }
        int ans = -1;
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] <= num) {
                ans = i;
            } else {
                break;
            }
        }
        return ans;
    }

    public static int[] randomArray(int maxLen, int maxValue) {
        int len = (int) (Math.random() * maxLen);
        int[] arr = new int[len];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = (int) (Math.random() * maxValue);
        }
        return arr;
    }

    public static void main(String[] args) {
        int maxLen = 20;
        int maxValue = 10000;
        int testTimes = 10000;
        for (int i = 0; i < testTimes; i++) {
            int[] arr = randomArray(maxLen, maxValue);
            int num = (int) (Math.random() * (maxValue + 1)) - (int) (Math.random() * maxValue);
            Arrays.sort(arr);
            if (checkNearLeft(arr, num) != bsNearLeft(arr, num)) {
                System.out.println(">=num最左 的 结果出错");
                System.out.println(Arrays.toString(arr));
                System.out.println(num);
                System.out.println(checkNearLeft(arr, num));
                System.out.println(bsNearLeft(arr, num));
                break;
            }
            if (checkNearRight(arr, num) != bsNearRight(arr, num)) {
                System.out.println("<=num最右 结果出错");
            }
        }
    }
}
  1. 局部最小值问题 ----》 无序 ,相邻不等
    前提: 数组无序,且任意两个相邻的位置 数值不相等(arr[i] != arr[i + 1], i [0, arr.length - 2])

局部最小的定义:
(1)左边界局部最小:arr[0] < arr[1];
(2)右边界局部最小:arr[N - 1] < arr[N -2];
(3)普遍(中间):arr[i - 1] > arr[i] < arr[i + 1];
(4) (可以理解为左边界左边或右边界右边 大小为无限大)此条可忽略,coding时 可利用这种思路

问题: 找到第一个局部最小值?

解决方法: 二分法
个人代码:

package class03;

import java.util.Arrays;

public class BSAwesome {
    // arr 无序
    // arr 相邻元素 不相等
    // 功能: 找出arr中 任意局部最小的index索引
    // 输入: 数组  ---> int[] arr
    // 输出: 局部最小的元素的索引下标 ---> int类型
    public static int oneMinIndex(int[] arr) {
        // 特殊情况 ---> summary: 一般对 arr == null, length == 0, length == 1,进行特殊处理
        if (arr == null || arr.length == 0) {
            // 返回 -1 表示无目标元素
            return -1;
        }
        int N = arr.length;
        // 长度为 1 ---> 直接返回唯一元素下标 0
        if (N == 1) {
            return 0;
        }
        // 考虑 length == 2 的情况 ---> length >= 2时, 统一处理
        //首先确定左右边界,是否为局部最小
        if (arr[0] < arr[1]) {
            return 0;
        }
        if (arr[N - 1] < arr[N - 2]) {
            return N - 1;
        }
        // 如果边界都不为局部最小,则 [L, R]中,必有局部最小,因为(1)相邻元素不相等,必有一大一小
        // (2) 画一下图就可知道,必有 先向下,再向上 的拐点
        int L = 0;
        int R = arr.length - 1;
        int ans = -1;
        // 开始二分法  ---> 为什么也可以考虑到二分,因为它也是在找数字,具体看solution
        // 这里,我们要对 arr进行二分,对每个arr[mid] 进行局部最小的比较,注意 考虑边界导致的特殊情况
        // 特殊处理理解: 数组中的任何一个元素,都有可能二分到,所以要考虑任何一个元素 是否出现越界的情况
        while (L <= R) {
            int mid = (L + R) / 2;
            // 特殊情况要先处理
            // 左边界
            if (mid == 0) {
                L = mid + 1;
                // 一定要注意, 执行完之后,不添加continue 还会继续往后做,出现越界
                continue;
            }
            if (mid == N - 1) {
                R = mid - 1;
                continue;
            }
            // 现在 出现的所有元素 都是有左右元素
            if (arr[mid] < arr[mid + 1] && arr[mid] < arr[mid - 1]) {
                ans = mid;
                break;
            } else if (arr[mid] > arr[mid - 1]) {
                // 只要arr[mid] > arr[mid - 1], 那么[L, mid - 1]一定存在局部最小
                R = mid - 1;
            } else { // arr[mid] > arr[mid + 1]
                L = mid + 1;
            }
        }
        return ans;
    }

    // Start 对数器 ---> 自动生成随机数样本进行比对的机器
    // 随机数组生成器 ---> 但是这次有要求, 无序->OK, 相邻不重复->(improve)修改下代码
    public static int[] randomArray(int maxLen, int maxValue) {
        // 生成数组
        int len = (int) (Math.random() * maxLen);
        int[] arr = new int[len];
        // 开始赋值 , 首先遍历数组 然后赋值
        // 首先特殊情况
        if (len == 0) {
            return arr;
        }
        // 开始正常情况, 首先赋值arr[0]
        arr[0] = (int) (Math.random() * maxValue);
        // 注意i 从 1 开始   ----> 然后赋值 arr[1] -> arr[N - 1]
        for (int i = 1; i < arr.length; i++) {
            do {
                arr[i] = (int) (Math.random() * maxValue);
            } while (arr[i] == arr[i - 1]);
        }
        return arr;
    }

    // 比对器 (两个方向 : 一个是生成正确结果,然后与测试函数进行比较; 另一个是将测试函数的结果作为参数,进行正误校验) ---->
    public static boolean check(int[] arr, int minIndex) {
        // 特殊情况处理
        if (arr.length == 0) {
            return minIndex == -1;
        }
        int left = minIndex - 1;
        int right = minIndex + 1;
        // 简化代码 --> boolean ---> (左边界结果 & 右边界结果) ---> 从而可以分开处理
        boolean leftResult = left >= 0 ? arr[minIndex] < arr[left] : true;
        // minIndex < arr.length - 1
        boolean rightResult = right < arr.length ? arr[minIndex] < arr[right] : true;
        return leftResult & rightResult;
    }

    public static void main(String[] args) {
        int maxLen = 100;
        int maxValue = 200;
        int testTime = 10000;
        System.out.println("测试开始");
        for (int i = 0; i < testTime; i++) {
            int[] arr = randomArray(maxLen, maxValue);
            int ans = oneMinIndex(arr);
            boolean succeed = check(arr, ans);
            if (!succeed) {
                Arrays.toString(arr);
                System.out.println("测试失败");
                return;
            }
        }
        System.out.println("测试成功");
    }
}

第二版本: 左神代码

package class03;

public class Code04_BSAwesome {

    // arr 整体无序
    // arr 相邻的数不相等!
    public static int oneMinIndex(int[] arr) {
        if (arr == null || arr.length == 0) {
            return -1;
        }
        int N = arr.length;
        if (N == 1) {
            return 0;
        }
        if (arr[0] < arr[1]) {
            return 0;
        }
        if (arr[N - 1] < arr[N - 2]) {
            return N - 1;
        }
        int L = 0;
        int R = N - 1;
        // L...R 肯定有局部最小
        while (L < R - 1) { // 确保有三个元素
            int mid = (L + R) / 2;
            if (arr[mid] < arr[mid - 1] && arr[mid] < arr[mid + 1]) {
                return mid;
            } else {
                if (arr[mid] > arr[mid - 1]) {
                    R = mid - 1;
                } else {
                    L = mid + 1;
                }
            }
        }
        return arr[L] < arr[R] ? L : R;
    }

    // 生成随机数组,且相邻数不相等
    public static int[] randomArray(int maxLen, int maxValue) {
        int len = (int) (Math.random() * maxLen);
        int[] arr = new int[len];
        if (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;
    }

    // 也用于测试
    public static boolean check(int[] arr, int minIndex) {
        if (arr.length == 0) {
            return minIndex == -1;
        }
        int left = minIndex - 1;
        int right = minIndex + 1;
        boolean leftBigger = left >= 0 ? arr[left] > arr[minIndex] : true;
        boolean rightBigger = right < arr.length ? arr[right] > arr[minIndex] : true;
        return leftBigger && rightBigger;
    }

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

    public static void main(String[] args) {
        int maxLen = 100;
        int maxValue = 200;
        int testTime = 1000000;
        System.out.println("测试开始");
        for (int i = 0; i < testTime; i++) {
            int[] arr = randomArray(maxLen, maxValue);
            int ans = oneMinIndex(arr);
            if (!check(arr, ans)) {
                printArray(arr);
                System.out.println(ans);
                break;
            }
        }
        System.out.println("测试结束");
        
    }
}

总结:
二分是不是一定要有序? 不是
在某一种标准下,比如局部最小问题, 只要你能保证有一侧肯定有,你就可以使用二分法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值