十、二分查找
二分查找最关键的地方在于,找到可以进行二分的结果集
10.1 查找插入位置
给定一个排序的整数数组 nums
和一个整数目标值 target
,请在数组中找到 target
,并返回其下标。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n)
的算法。
示例 1:
- 输入: nums = [1,3,5,6], target = 5
- 输出: 2
提示:
1 <= nums.length <= 10^4
-10^4 <= nums[i] <= 10^4
- nums 为无重复元素的升序排列数组
-10^4 <= target <= 10^4
思路: 简单二分查找,查找目标值
注意点: 当数组中不存在target
时,判断目标值和nums[l]
对应的值大小,从而判断插入的位置是否+1
class Solution {
public int searchInsert(int[] nums, int target) {
int l = 0, r = nums.length -1;
while(l < r){
int mid = l + ((r - l) >> 1);
if(nums[mid] > target){
r = mid -1;
}else if(nums[mid] < target){
l = mid + 1;
}else{
return mid;
}
}
return target > nums[l] ? l + 1 : l;
}
}
10.2山峰数组的顶部
符合下列属性的数组 arr 称为 山峰数组(山脉数组) :
-
arr.length >= 3
-
存在 i(
0 < i < arr.length - 1
)使得:-
arr[0] < arr[1] < ... arr[i-1] < arr[i]
-
arr[i] > arr[i+1] > ... > arr[arr.length - 1]
给定由整数组成的山峰数组 arr ,返回任何满足
arr[0] < arr[1] < ... arr[i - 1] < arr[i] > arr[i + 1] > ... > arr[arr.length - 1]
的下标 i ,即山峰顶部。 -
示例 1:
- 输入:arr = [0,1,0]
- 输出:1
提示:
3 <= arr.length <= 104
0 <= arr[i] <= 106
- 题目数据保证
arr
是一个山脉数组
思路: 二分查找,比较当前下标对应值和下一个数值的大小,寻找山峰
注意点: 用l < r
可以使得 mid+1
不会越界
class Solution {
public int peakIndexInMountainArray(int[] arr) {
int len = arr.length;
int l = 0, r = len -1;
while(l < r){
int mid = l + ((r - l) >> 1);
if(arr[mid] < arr[mid + 1]){
l = mid + 1;
}else{
r = mid;
}
}
return l;
}
}
10.3 排序数组中只出现一次的数字
给定一个只包含整数的有序数组 nums
,每个元素都会出现两次,唯有一个数只会出现一次,请找出这个唯一的数字。
你设计的解决方案必须满足 O(log n)
时间复杂度和 O(1)
空间复杂度。
示例 1:
- 输入: nums = [1,1,2,3,3,4,4,8,8]
- 输出: 2
提示:
1 <= nums.length <= 10^5
0 <= nums[i] <= 10^5
思路: 二分查找,因为两个相同的数字一定是相邻的,通过比较对应相邻元素数值是否相等,判断出现一次的元素的位置
注意点: 通过异或 可以将奇偶数两种情况全部考虑到
class Solution {
public int singleNonDuplicate(int[] nums) {
int l = 0, r = nums.length - 1;
if( l == r) return nums[l];
while(l < r){
int mid = l + ((r -l) >> 1);
// 通过异或找到 mid 对应的那对元素
if(nums[mid] == nums[mid ^ 1]){
l = mid + 1;
}else{
r = mid;
}
}
return nums[l];
}
}
10.4求平方根
给定一个非负整数 x ,计算并返回 x 的平方根,即实现 int sqrt(int x)
函数。
正数的平方根有两个,只输出其中的正数平方根。
如果平方根不是整数,输出只保留整数的部分,小数部分将被舍去。
示例 1:
- 输入: x = 4
- 输出: 2
提示:
0 <= x <= 2^31 - 1
思路: 二分查找,从1~ x 之间查找 一个数的平方与 x的大小关系不断缩小查找范围
注意点: 使用 mid
与x / mid
进行比较防止溢出
class Solution {
public int mySqrt(int x) {
int l = 1, r = x;
while( l <= r){
int mid = l + ((r - l) >> 1);
if(mid > x / mid){
r = mid -1;
}else{
l = mid + 1;
}
}
return r;
}
}
10.5 按权重生成随机数
给定一个正整数数组 w ,其中 w[i]
代表下标 i 的权重(下标从 0 开始),请写一个函数 pickIndex
,它可以随机地获取下标 i,选取下标 i 的概率与w[i]
成正比。
例如,对于 w = [1, 3]
,挑选下标 0 的概率为 1 / (1 + 3) = 0.25
(即,25%),而选取下标 1 的概率为 3 / (1 + 3) = 0.75
(即,75%)。
也就是说,选取下标 i 的概率为 w[i] / sum(w)
。
- 示例 1:
输入:
- inputs = [“Solution”,“pickIndex”]
- inputs = [[[1]],[]]
输出:
- [null,0]
解释:
Solution solution = new Solution([1]);
solution.pickIndex();
// 返回 0,因为数组中只有一个元素,所以唯一的选择是返回下标 0。
提示:
1 <= w.length <= 10000
1 <= w[i] <= 10^5
pickIndex
将被调用不超过10000
次
思路: 对数组求前缀和,元素大于0保证递增,元素总和为sum,对[1,sum]中的随机整数进行二分查找,找到数组中第一个大于等于随机数的值,返回下标
注意点: 随机数范围是[1,sum],如果不加1会出现0的情况
class Solution {
int[] w;
int sum;
private static final Random random = new Random();
public Solution(int[] w) {
this.w = w;
int len = w.length;
for(int i =1; i < len; i++){
// 前缀和
w[i] += w[i-1];
}
// 总和
sum = w[len -1];
}
public int pickIndex() {
int target = random.nextInt(sum) + 1; // [1,sum]范围中的随机整数
int l =0 ,r = w.length-1;
while(l <= r){
int mid = l + ((r - l) >> 1);
if(w[mid] < target){
l = mid + 1;
}else{
r = mid - 1;
}
}
return l;
}
}
10.6狒狒吃香蕉
狒狒喜欢吃香蕉。这里有 n 堆香蕉,第 i 堆中有 piles[i]
根香蕉。警卫已经离开了,将在 h 小时后回来。
狒狒可以决定她吃香蕉的速度 k (单位:根/小时)
。每个小时,她将会选择一堆香蕉,从中吃掉 k 根。如果这堆香蕉少于 k 根,她将吃掉这堆的所有香蕉,然后这一小时内不会再吃更多的香蕉,下一个小时才会开始吃另一堆的香蕉。
狒狒喜欢慢慢吃,但仍然想在警卫回来前吃掉所有的香蕉。
返回她可以在 h 小时内吃掉所有香蕉的最小速度 k(k 为整数)
。
示例 1:
- 输入:piles = [3,6,7,11], h = 8
- 输出:4
提示:
1 <= piles.length <= 104
piles.length <= h <= 109
1 <= piles[i] <= 109
思路: 找到数组最大值max,然后对[1,max]
进行二分,求出吃完所有香蕉所需时间,然后和h进行比较,判断是否加速或减速,最终找到在h时间内吃完的最小速度
注意点: 求某一速度下吃一堆香蕉花费的时间,需要向上取整
class Solution {
public int minEatingSpeed(int[] piles, int h) {
int max = 0;
for(int num : piles){
max = Math.max(num,max);
}
int l = 1, r = max;
while(l <= r){
// 速度
int mid = l + ((r - l) >> 1);
// 时间
int ans = 0;
for(int num : piles){
ans += (num - 1) / mid + 1; //向上取整
}
if(ans > h){
l = mid + 1; //加快速度
}else{
r = mid - 1; // 降低速度
}
}
return l;
}
}