二分查找的基础知识
在递增排序数组中进行二分查找代码
public int search(int[] nums,int target){
int left = 0;
int right = nums.length - 1;
while(left <= right){
int mid = (left + right) / 2;
if(nums[mid] >= target){
if(nums[mid] == target){
return nums[mid];
}
right = mid - 1;
}else{
left = mid + 1;
}
}
return -1;
}
在排序数组中二分查找
关键词:排序数组、查找
Q68:查找插入位置
题目(简单):给定一个排序的整数数组 nums 和一个整数目标值 target ,请在数组中找到 target ,并返回其下标。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。请必须使用时间复杂度为 O(log n) 的算法。 示例 1:
输入: nums = [1,3,5,6], target = 5
输出: 2
示例 2:
输入: nums = [1,3,5,6], target = 2
输出: 1
示例 3:
输入: nums = [1,3,5,6], target = 7
输出: 4
解题思路
注意两种特殊情况
target比所有数字小,返回0target比所有数字大,返回nums.length
public int searchInsert(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while(left <= right){
int mid = (left + right) / 2;
if(target <= nums[mid]){
if(mid == 0 || target > nums[mid - 1]) return mid;
right = mid - 1;
}else{
left = mid + 1;
}
}
return nums.length;
}
Q69:山峰数组的顶部
题目(简单):符合下列属性的数组 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
示例 2:
输入:arr = [1,3,5,4,2]
输出:2
示例 3:
输入:arr = [0,10,5,2]
输出:1
示例 4:
输入:arr = [3,4,5,1]
输出:2
示例 5:
输入:arr = [24,69,100,99,79,78,67,36,26,19]
输出:2
public int peakIndexInMountainArray(int[] arr) {
//注意左右初始条件
int left = 1;
int right = arr.length - 2;
while(left <= right){
int mid = (left + right) / 2;
if(arr[mid] > arr[mid + 1] && arr[mid] > arr[mid - 1]) return mid;
if(arr[mid] > arr[mid - 1]){
left = mid + 1;
}else{
right = mid - 1;
}
}
return -1;
}
Q70:排序数组中只出现一次的数字
题目(中等):给定一个只包含整数的有序数组 nums ,每个元素都会出现两次,唯有一个数只会出现一次,请找出这个唯一的数字。
示例 1:
输入: nums = [1,1,2,3,3,4,4,8,8]
输出: 2
示例 2:
输入: nums = [3,3,7,7,10,11,11]
输出: 10
解题思路
- 先将数组每两个一组
- 判断第mid组的两个元素是否相等,若不相等
- mid==0,那么就是第一组不相等的
- 前一组两元素相等,则当前就是第一组不相等的
- 否则,第一组不相等的就在前半段
public int singleNonDuplicate(int[] nums) {
int left = 0;
int right = nums.length / 2;
while(left <= right){
int mid = (left + right) / 2;
int i = 2 * mid;
if(i < nums.length - 1 && nums[i] != nums[i + 1]){
if(mid == 0 || nums[i-2] == nums[i-1]){
return nums[i];
}
right = mid - 1;
}else{
left = mid + 1;
}
}
return nums[nums.length - 1];
}
Q71:按权重生成随机数
题目(中等):给定一个正整数数组 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。
示例 2:
输入:
inputs = ["Solution","pickIndex","pickIndex","pickIndex","pickIndex","pickIndex"]
inputs = [[[1,3]],[],[],[],[],[]]
输出:
[null,1,1,1,1,0]
解释:
Solution solution = new Solution([1, 3]);
solution.pickIndex(); // 返回 1,返回下标 1,返回该下标概率为 3/4 。
solution.pickIndex(); // 返回 1
solution.pickIndex(); // 返回 1
solution.pickIndex(); // 返回 1
solution.pickIndex(); // 返回 0,返回下标 0,返回该下标概率为 1/4 。
由于这是一个随机问题,允许多个答案,因此下列输出都可以被认为是正确的:
[null,1,1,1,1,0]
[null,1,1,1,1,1]
[null,1,1,1,0,0]
[null,1,1,1,0,1]
[null,1,0,1,0,0]
......
解题思路
创建一个权重数组
class Solution {
private int[] sum;
private int total;
public Solution(int[] w) {
sum = new int[w.length];
for(int i = 0;i < w.length;i++){
total += w[i];
sum[i] = total;
}
}
public int pickIndex() {
Random random = new Random();
int p = random.nextInt(total);
int left = 0;
int right = sum.length - 1;
while(left <= right){
int mid = (left + right) / 2;
if(sum[mid] > p){
if(mid == 0 || sum[mid - 1] <= p){
return mid;
}
right = mid - 1;
}else{
left = mid + 1;
}
}
return -1;
}
}
在数值范围内二分查找
关键两点:
- 确定解的范围
- 发现不是中间值之后,选择前半段还是后半段
Q72:求平方根
题目(简单):给定一个非负整数 x ,计算并返回 x 的平方根,即实现 int sqrt(int x) 函数。正数的平方根有两个,只输出其中的正数平方根。如果平方根不是整数,输出只保留整数的部分,小数部分将被舍去。
示例 1:
输入: x = 4
输出: 2
示例 2:
输入: x = 8
输出: 2
解释: 8 的平方根是 2.82842...,由于小数部分将被舍去,所以返回 2
public int mySqrt(int x) {
int left = 1;//若从0开始会产生分母为0情况
int right = x;
while (left <= right){
int mid = (left + right) / 2;
if(mid <= x / mid){//用mid*mid<=x会造成溢出
if((mid+1) > x / (mid+1)){
return mid;
}
left = mid + 1;
}else{
right = mid - 1;
}
}
return 0;
}
Q73:狒狒吃香蕉
题目(中等):狒狒喜欢吃香蕉。这里有 N 堆香蕉,第 i 堆中有 piles[i] 根香蕉。警卫已经离开了,将在 H 小时后回来。狒狒可以决定她吃香蕉的速度 K (单位:根/小时)。每个小时,她将会选择一堆香蕉,从中吃掉 K 根。如果这堆香蕉少于 K 根,她将吃掉这堆的所有香蕉,然后这一小时内不会再吃更多的香蕉,下一个小时才会开始吃另一堆的香蕉。 狒狒喜欢慢慢吃,但仍然想在警卫回来前吃掉所有的香蕉。返回她可以在 H 小时内吃掉所有香蕉的最小速度 K(K 为整数)。
示例 1:
输入: piles = [3,6,7,11], H = 8
输出: 4
示例 2:
输入: piles = [30,11,23,4,20], H = 5
输出: 30
示例 3:
输入: piles = [30,11,23,4,20], H = 6
输出: 23
public int minEatingSpeed(int[] piles, int h) {
int max = Integer.MIN_VALUE;
for (int pile : piles){
max = Math.max(max,pile);
}
int left = 1;
int right = max;
while(left <= right){
int mid = (left + right) / 2;
int hour = getHour(piles,mid);
if(hour <= h){
if(mid == 1 || getHour(piles,mid-1)>h){
return mid;
}
right = mid - 1;
}else{
left = mid + 1;
}
}
return -1;
}
private int getHour(int[] piles,int speed){
int hour = 0;
for(int pile : piles){
hour += (pile + speed - 1) / speed;//上取整
}
return hour;
}
小结
基本思路:
在查找范围内选取位于中间的数字,判断是否满足要求,若不满足则确定下一轮的查找范围。
应用场合:
- 用于排序数组中查找某一个数字
- 在数值范围内实现快速查找
- 确定查找范围
- 尝试中间值 ···