一、二分基础知识

1. 概念

  在一个长度为 n 的数组中查找一个数字,如果逐一扫描数组中的每 个数字,那么需要 O(n) 的时间。
  如果数组是排序的(通常按照递增的顺序排序),那么可以采用 二分查找算法进行优化。可以取出位于数组中间的数字并和目标数字比较。如果中间数字正好等于目标数字,那么就找到了目标数字。如果中间数字大于目标数字,那么只需要查找数组的前半部分,这是因为数组是排序的,后半部分的数字都大于或等于中间数字,所以一定都大于目标数字,也就没有必要再在后半部分查找。如果中间数字小于目标数字,那么接下来只需要查找数组的后半部分,这是因为排序数组的前半部分的数字都小于或等于中间数字,所以一定都小于目标数字,也就没有必要再在前半部分查找。
  参考代码如下:

public static int search(int[] nums, int target) {
    int start = 0, end = nums.length - 1;
    while (start <= end) {
        int mid = (start + end) / 2;
        if (nums[mid] == target) {
            return mid;
        } else if (nums[mid] > target) {
            end = mid - 1;
        } else {
            start = mid + 1;
        }
    }
    return -1;
}

二、二分知识扩展

1. 排序数组中的二分

知识扩展

  在数组中查找一个数字原本是一件非常直观的事情,逐一扫描数组中的数字即可。但如果数组是有序的集合,那么非常建议使用二分查找进行处理。

题目练习

Ⅰ:查找插入位置
题目描述

  略输入一个排序的整数数组 nums 和一个目标值 t ,如果数组 nums 中包含 t ,则返回 t 在数组中的下标;如果数组 nums 中不包含 t ,则返回将 t 按顺序插入数组 nums 中的下标。假设数组中没有相同的数字。例如,输入数组 nums 为 [1,3,6,8],如果目标值 t 为 3,则输出1;如果 t 为 5,则返回 2。

题目分析

难度: ⭐🐇🐇🐇🐇
思路:
  这是一个典型的二分查找题目,不同在于,找不到相同元素时需要返回插入的下标位置。分析二分查找代码不难发现,start 其实记录的正好就是这个需要插入的下标,即只需要将最后的 return -1 更换为 return start。
  因为在找不到目标元素时,start 和 end 一定会在最接近目标的元素的旁边相遇,相遇时,我们所需要求得的下标便是相遇位置左移或者右移一位的下标位置,当相遇的位置所处元素值小于目标元素,那么插入下标应该是当前下标的下一位,而此时 start 正好右移;而如果大于目标元素时,说明应该在当前位置插入,而此时 start 正好不移动,所以此时二分查找后 start 就是应该插入的位置下标。
  当然可以从边界条件进行分析,如果 mid 指向元素 < 目标元素,那么目标位置一定在 mid + 1 --> end 之中;如果 mid 指向元素 >= 目标元素,且 mid - 1 指向元素 < 目标元素,那么 mid 即为所求;如果此时 mid - 1 不满足,则所求元素一定在 start --> mid -1 之中。

参考代码
// 二分改
public static int findIndex(int[] nums, int target) {
    int start = 0, end = nums.length - 1;
    while (start <= end) {
        int mid = (start + end) / 2;
        if (nums[mid] == target) {
            return mid;
        } else if (nums[mid] > target) {
            end = mid - 1;
        } else {
            start = mid + 1;
        }
    }
    return start;
}

// 另一种思路
public static int findIndex2(int[] nums, int target) {
    int start = 0, end = nums.length - 1;
    while (start <= end) {
        int mid = (start + end) / 2;
        if (nums[mid] >= target) {
            // mid = 0 表示数组元素都大于目标
            if (mid == 0 || nums[mid - 1] < target) {
                return mid;
            }
            end = mid - 1;
        } else {
            start = mid + 1;
        }
    }
    // 数组元素都小于目标时,返回长度即可
    return nums.length;
}
Tips:2025-06-20

Ⅱ:山峰数组的顶部
题目描述

  在一个长度大于或等于 3 的数组中,任意相邻的两个数字都不相等。该数组的前若干数字是递增的,之后的数字是递减的,因此它的值看起来像一座山峰。请找出山峰顶部,即数组中最大值的位置。例如,在数组 [1,3,5,4,2] 中,最大值是 5,输出它在数组中的下标 2。

题目分析

难度: ⭐🐇🐇🐇🐇
思路: 暴力的方式暂不说明;这依旧是一个二分的典型题目,只不过确认确认范围的条件有所改变,我们需要根据元素的前一个元素和后一个元素确定,当前元素是属于递增部分还是递减部分,如果是递增部分,那么峰值一定在 mid + 1 --> end 之中,否则一定在 start --> mid - 1 之中;而如果元素的前一个元素小于自己,后一个元素也小于自己,那么当前元素即为所求。

参考代码
public static int findTopIndex(int[] nums) {
    // 由于求的是峰值,那么元素数量一定大于 1,左右边界可以往中间靠 1
    int start = 1, end = nums.length - 2;
    while (start <= end) {
        int mid = (start + end) / 2;
        if (nums[mid] > nums[mid + 1] && nums[mid] > nums[mid - 1]) {
            return mid;
        }
        if (nums[mid] > nums[mid + 1]) {
            end = mid - 1;
        } else {
            start = mid + 1;
        }
    }
    return -1;
}
Tips:2025-06-20

Ⅲ:排序数组中只出现一次的数字
题目描述

  在一个排序的数组中,除一个数字只出现一次之外,其他数字都出现了两次,请找出这个唯一只出现一次的数字。例如,在数组 [1,1,2,2,3,4,4,5,5] 中,数字 3 只出现了一次。

题目分析

难度: ⭐⭐🐇🐇🐇
思路: 如果不存在出现依次的元素,那么两两进行分组时,各组内元素一定相同。而加入出现一次的元素后,从该元素开始,后续的组合两元素将不再相同。此时如果找到第一次两元素不同的组合后,改组合的首元素即为所求。判断是否为第一次出现差异的组的依据便是前一组的两个元素相等。

参考代码
public int findSingleIndex(int[] nums) {
    // 将数组两两分组,而比较元素时乘以 2 得出原下标
    int start = 0, end = nums.length / 2;
    while (start <= end) {
        int mid = (start + end) / 2;
        int i = mid * 2;
        if (i < nums.length - 1 && nums[i] != nums[i + 1]) {
            // 如果前一组值相等,说明当前组为第一组出现不同的组合
            if (mid == 0 || nums[mid - 2] == nums[i - 1]) {
                return nums[i];
            }
            end = mid - 1;
        } else {
            start = mid + 1;
        }
    }
    return nums[nums.length - 1];
}
Tips:2025-06-20

Ⅳ:按权重生成随机数
题目描述

  输入一个正整数数组 w,数组中的每个数字 w[i] 表示下标 i 的权重,请实现一个函数 pickIndex 根据权重比例随机选择一个下标。例如,如果权重数组 w 为 [1,2,3,4],那么函数 pickIndex 将有 10% 的概率选择 0、20% 的概率选择 1、30% 的概率选择 2、40% 的概率选择 3。

题目分析

难度:
思路:

参考代码

Tips:-

2. 数值范围内的二分

知识扩展

  略

题目练习

Ⅰ:求平方根
题目描述

  输入一个非负整数,请计算它的平方根。正数的平方根有两个,只输出其中的正数平方根。如果平方根不是整数,那么只需要输出它的整数部分。例如,如果输入 4 则输出 2;如果输入18 则输出 4。

题目分析

难度:
思路:

参考代码

Tips:-

Ⅱ:狒狒吃香蕉
题目描述

  狒狒很喜欢吃香蕉。一天它发现了 n 堆香蕉,第 i 堆有 piles[i] 根香蕉。门卫刚好走开,H 小时后才会回来。狒狒吃香蕉喜欢细嚼慢咽,但又想在门卫回来之前吃完所有的香蕉。请问狒狒每小时至少吃多少根香蕉?如果狒狒决定每小时吃 k 根香蕉,而它在吃的某一堆剩余的香蕉的数目少于 k,那么它只会将这一堆的香蕉吃完,下一个小时才会开始吃另一堆的香蕉。

题目分析

难度:
思路:

参考代码

Tips:-

三、章节小结

  略