二分查找详解

题目一:在排序数组中查找元素的第⼀个和最后⼀个位置

1. 题⽬链接:

https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/description/

2. 题⽬描述:

给你⼀个按照⾮递减顺序排列的整数数组 nums,和⼀个⽬标值 target。请你找出给定⽬标值在数组
中的开始位置和结束位置。
如果数组中不存在⽬标值 target,返回 [-1, -1]。
你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。 ⽰例 1:
输⼊:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
⽰例 2:
输⼊:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
⽰例 3:
输⼊:nums = [], target = 0
输出:[-1,-1]

提⽰:
0 <= nums.length <= 105
-109 <= nums[i] <= 109
nums 是⼀个⾮递减数组
-109 <= target <= 109

3. 算法思路:

⽤的是⼆分思想,就是根据数据的性质,在某种判断条件下将区间⼀分为⼆,然后舍去其中⼀个
区间,然后再另⼀个区间内查找;
⽅便叙述,⽤ x 表⽰该元素, resLeft 表⽰左边界, resRight 表⽰右边界。
寻找左边界思路:

寻找左边界:

我们注意到以左边界划分的两个区间的特点:

▪左边区间 [left, resLeft - 1] 都是⼩于 x 的;

▪ 右边区间(包括左边界) [resLeft, right] 都是⼤于等于 x 的;

• 因此,关于 mid 的落点,我们可以分为下⾯两种情况:

当我们的 mid 落在 [left, resLeft - 1] 区间的时候,也就是 arr[mid] < target 。说明
[left, mid] 都是可以舍去的,此时更新 left 到 mid + 1 的位置, 继续在 [mid + 1, right]
上寻找左边界;

◦ 当 mid 落在 [resLeft, right] 的区间的时候,也就是 arr[mid] >= target 。 说明
[mid + 1, right] (因为 mid 可能是最终结果,不能舍去)是可以舍去的,此时 更新 right 到 mid
的位置,继续在 [left, mid] 上寻找左边界; • 由此,就可以通过⼆分,来快速寻找左边界; 注意:这⾥找中间元素需要向下取整。
因为后续移动左右指针的时候:

• 左指针: left = mid + 1 ,是会向后移动的,因此区间是会缩⼩的;

• 右指针: right = mid ,可能会原地踏步(⽐如:如果向上取整的话,如果剩下 1,2 两个元 素, left == 1 , right == 2 , mid == 2 。更新区间之后, left,right,mid 的值没有改变,就会陷⼊死循环)。
因此⼀定要注意,当 right = mid 的时候,要向下取整。 寻找右边界思路:

• 寻右左边界:

◦ ⽤ resRight 表⽰右边界;

◦ 我们注意到右边界的特点:

▪ 左边区间 (包括右边界) [left, resRight] 都是⼩于等于 x 的;

▪ 右边区间 [resRight+ 1, right] 都是⼤于 x 的;

• 因此,关于 mid 的落点,我们可以分为下⾯两种情况:

◦ 当我们的 mid 落在 [left, resRight] 区间的时候,说明 [left, mid - 1]

( mid 不可以舍去,因为有可能是最终结果) 都是可以舍去的,此时更新 left 到 mid

的位置; ◦ 当 mid 落在 [resRight+ 1, right] 的区间的时候,说明 [mid, right] 内的元素
是可以舍去的,此时更新 rightmid - 1 的位置;

• 由此,就可以通过⼆分,来快速寻找右边界; 注意:这⾥找中间元素需要向上取整。 因为后续移动左右指针的时候:

• 左指针: left = mid ,可能会原地踏步(⽐如:如果向下取整的话,如果剩下 1,2 两个元 素, left == 1, right == 2,mid == 1 。更新区间之后, left,right,mid 的值 没有改变,就会陷⼊死循环)。

• 右指针: right = mid - 1 ,是会向前移动的,因此区间是会缩⼩的; 因此⼀定要注意,当 right = mid
的时候,要向下取整。 ⼆分查找算法总结:请⼤家⼀定不要觉得背下模板就能解决所有⼆分问题。⼆分问题最重要的就是要分析题意,然后确定
要搜索的区间,根据分析问题来写出⼆分查找算法的代码。

模板记忆技巧:

  1. 关于什么时候⽤三段式,还是⼆段式中的某⼀个,⼀定不要强⾏去⽤,⽽是通过具体的问题分析情 况,根据查找区间的变化确定指针的转移过程,从⽽选择⼀个模板。
  2. 当选择两段式的模板时:
    在求 mid 的时候,只有 right - 1 的情况下,才会向上取整(也就是 +1 取中间数)

4.代码

class Solution {
    public int[] searchRange(int[] nums, int target) {
    
     int[] ret = new int[2];
     ret[0]=-1;
     ret[1]=-1;
         if(nums.length==0) return ret;
        int n = nums.length;
        int left = 0;
        int right = n-1;
        //左端点
        while(left < right){
            int mid = left+(right-left)/2;
            if(nums[mid] < target){
                left = mid+1;
            }else if(nums[mid] >= target){
                right = mid;
            }
        }
        if(nums[left] == target)
        ret[0] = left;

//右端点
right = n-1;
while(left < right){
    int mid=left+(right-left+1)/2;
    if(nums[mid] <= target){
        left = mid;
    }else if(nums[mid] > target){
    right=mid-1;
    }
}
if(nums[left]==target)
ret[1] = left;
return ret;
    }
}

题目二:搜索插⼊位置

1. 题⽬链接:

https://leetcode.cn/problems/search-insert-position/description/

2. 题⽬描述:

给定⼀个排序数组和⼀个⽬标值,在数组中找到⽬标值,并返回其索引。如果⽬标值不存在于数组
中,返回它将会被按顺序插⼊的位置。
请必须使⽤时间复杂度为 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

3. 解法(⼆分查找算法):

算法思路:

a. 分析插⼊位置左右两侧区间上元素的特点:设插⼊位置的坐标为 index ,根据插⼊位置的特点可以知道:

[left, index - 1] 内的所有元素均是⼩于 target 的;

[index, right] 内的所有元素均是⼤于等于 target 的。

b.left 为本轮查询的左边界, right 为本轮查询的右边界。根据 mid 位置元素的信 息,分析下⼀轮查询的区间:

▪ 当 nums[mid] >= target 时,说明 mid 落在了 [index, right] 区间上,mid 左边包括
mid 本⾝,可能是最终结果,所以我们接下来查找的区间在 [left, mid] 上。因此,更新 right 到 mid
位置,继续查找。

▪ 当 nums[mid] < target 时,说明 mid 落在了 [left, index -1] 区间上,

mid 右边但不包括 mid 本⾝,可能是最终结果,所以我们接下来查找的区间在 [mid + 1, right] 上。因此,更新
left 到 mid + 1 的位置,继续查找。

c. 直到我们的查找区间的⻓度变为 1 ,也就是 left == right 的时候, left 或者 right
所在的位置就是我们要找的结果。

代码

class Solution 
{
 public int searchInsert(int[] nums, int target) 
 {
 int left = 0, right = nums.length - 1;
 while(left < right)
 {
 int mid = left + (right - left) / 2;
 if(nums[mid] < target) left = mid + 1;
 else right = mid;
 }
 // 特判⼀下第三种情况 
 if(nums[right] < target) return right + 1;
 return right;
 }
}


题目三: x 的平⽅根

1. 题⽬链接:

https://leetcode.cn/problems/sqrtx/description/

2. 题⽬描述:

给你⼀个⾮负整数 x ,计算并返回 x 的 算术平⽅根 。
由于返回类型是整数,结果只保留 整数部分 ,⼩数部分将被 舍去 。
注意:不允许使⽤任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。
⽰例 1:
输⼊: x = 4

输出: 2

⽰例 2: 输⼊: x = 8

输出: 2

解释:

8 的算术平⽅根是 2.82842… , 由于返回类型是整数,⼩数部分将被舍去。

3. 解法⼀(暴⼒查找):
算法思路:

依次枚举 [0, x] 之间的所有数 i : (这⾥没有必要研究是否枚举到 x / 2 还是 x / 2 + 1
。因为我们找到结果之后直接就返回 了,往后的情况就不会再判断。反⽽研究枚举区间,既耽误时间,⼜可能出错)

▪ 如果 i * i == x ,直接返回 x ;

▪ 如果 i * i > x ,说明之前的⼀个数是结果,返回 i - 1 。 由于 i * i 可能超过 int 的最⼤值,因此使⽤
long long 类型。

算法代码:
class Solution {

public:
 int mySqrt(int x) {
 // 由于两个较⼤的数相乘可能会超过 int 最⼤范围 
 // 因此⽤ long long 
 long long i = 0;
 for (i = 0; i <= x; i++)
 {
 // 如果两个数相乘正好等于 x,直接返回 i 
 if (i * i == x) return i;
 // 如果第⼀次出现两个数相乘⼤于 x,说明结果是前⼀个数 
 if (i * i > x) return i - 1;
 }
 // 为了处理oj题需要控制所有路径都有返回值 
 return -1;
 }
};

4. 解法⼆(⼆分查找算法):

算法思路:

设 x 的平⽅根的最终结果为 index :

a. 分析 index 左右两次数据的特点:

▪ [0, index] 之间的元素,平⽅之后都是⼩于等于 x 的;

▪ [index + 1, x] 之间的元素,平⽅之后都是⼤于 x 的。 因此可以使⽤⼆分查找算法。

算法代码
class Solution {
    public int mySqrt(int x) {
        if(x <1)return 0;
long left = 1;
long right = x;

while(left<right){
    long mid = left+(right-left+1)/2;
    if(mid*mid<=x){
        left=mid;
    }else if(mid*mid>x){
        right=mid-1;
    }
    
}

return (int)left;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值