HOT100系列-二分查找类型题
核心思想
例题
1、搜索插入位置
题目描述:
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 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
提示:
1 <= nums.length <= 104-104 <= nums[i] <= 104nums为 无重复元素 的 升序 排列数组-104 <= target <= 104
解题思路:
- 二分搜索、
代码如下
Java
class Solution {
public int searchInsert(int[] nums, int target) {
int ans=bfind(nums,target);
return ans;
}
/**
在有序数组nums中找到大于等于target的第一个位置
*/
public int bfind(int[] nums, int target){
int left=0;
int right=nums.length-1;
int ans=nums.length;
int medin=0;
while(left<=right){
medin=((right-left)>>1)+left;
if(nums[medin]>=target){
right=medin-1;
ans=Math.min(ans,medin);
}else{
left=medin+1;
}
}
return ans;
}
}
2、搜索二维矩阵
题目描述:
给你一个满足下述两条属性的 m x n 整数矩阵:
- 每行中的整数从左到右按非严格递增顺序排列。
- 每行的第一个整数大于前一行的最后一个整数。
给你一个整数 target ,如果 target 在矩阵中,返回 true ;否则,返回 false 。
示例 1:

**输入:**matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 3
**输出:**true
示例 2:

**输入:**matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 13
**输出:**false
提示:
m == matrix.lengthn == matrix[i].length1 <= m, n <= 100-104 <= matrix[i][j], target <= 104
解题思路:
- 先找到target可能存在的哪一行
- 因为每行是升序的,因此我们从第一行开始遍历
- 若当前行的最后一个元素小于target,则直接进入下一行
- 若当前行的最后一个元素大于target,则说明target可能在当前行,对当前行进行二分搜索
- 对哪一行的元素用二分查找,判断target是否存在
代码如下:
Java
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int m=matrix.length;
int n=matrix[0].length;
for(int i=0;i<m;i++){
if(matrix[i][n-1]>=target){
return bsfind(matrix[i],target);
}
}
return false;
}
//查看数组中是否有target元素
public boolean bsfind(int[]nums,int target){
int right=nums.length-1;
int left=0;
int medin=0;
boolean ans=false;
while(left<=right){
medin=((right-left)>>1)+left;
if(nums[medin]>target){
right=medin-1;
}else if(nums[medin]<target){
left=medin+1;
}else{
ans=true;
break;
}
}
return ans;
}
}
3、在排序数组中查找元素的第一个和最后一个位置
题目描述:
给你一个按照非递减顺序排列的整数数组 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] <= 109nums是一个非递减数组-109 <= target <= 109
解题思路:
- 二分查找
- 查找第一个target元素位置,二分搜索>=target
- 查找最后一个target元素位置,二分搜索>target
代码如下:
Java
class Solution {
public int[] searchRange(int[] nums, int target) {
if(nums.length==0){
return new int[]{-1,-1};
}
int left=bsfindLeft(nums,target);
//判断数组中是否存在target元素
if(nums[left]!=target){
return new int[]{-1,-1};
}
int right=bsfindRight(nums,target);
//right必须大于等于left
right=right>left?right-1:left;
return new int[]{left,right};
}
//寻找大于等于target的最左位置
public int bsfindLeft(int[] nums, int target){
int right=nums.length-1;
int left=0;
int medin=0;
int ans=0;
while(left<=right){
medin=((right-left)>>1)+left;
if(nums[medin]>=target){
right=medin-1;
ans=medin;
}else{
left=medin+1;
}
}
return ans;
}
//寻找大于target的最左元素
public int bsfindRight(int[] nums, int target){
int right=nums.length-1;
int left=0;
int medin=0;
//初始化为数组长度
int ans=nums.length;
while(left<=right){
medin=((right-left)>>1)+left;
if(nums[medin]>target){
right=medin-1;
ans=medin;
}else{
left=medin+1;
}
}
return ans;
}
}
4、搜索旋转排序数组
题目描述:
整数数组 nums 按升序排列,数组中的值 互不相同 。
在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 向左旋转 ,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 下标 3 上向左旋转后可能变为 [4,5,6,7,0,1,2] 。
给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。
你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。
示例 1:
**输入:**nums = [4,5,6,7,0,1,2], target = 0
**输出:**4
示例 2:
**输入:**nums = [4,5,6,7,0,1,2], target = 3
输出:-1
示例 3:
**输入:**nums = [1], target = 0
输出:-1
提示:
1 <= nums.length <= 5000-104 <= nums[i] <= 104nums中的每个值都 独一无二- 题目数据保证
nums在预先未知的某个下标上进行了旋转 -104 <= target <= 104
解题思路:
- 由于数组进行旋转,整个数组其实被分为了2段 (2段升序)
- 因此对整个数组使用二分,很有可能出现当前查找medin和目标target不在同一段的问题
- 这个时候我们要干的就是将而二分和target放在同一段中进行
- 如何判断是否在同一段呢?
- 数组最后一个元素的值,二分搜索的值,target的值的关系可以辅助判断
代码如下:
Java
class Solution {
public int search(int[] nums, int target) {
int right=nums.length-1;
int left=-1;
int last=nums[right];
while(left+1<right){
int medin=(left+right)>>1;
int x=nums[medin];
//接下来根据target和medin的元素是否在同一段进行判断
// target 在第一段,x 在第二段
if(target>last && x<=last){
right=medin;
}else if(target<=last && x>last){
// x 在第一段,target 在第二段
left=medin;
}else if(x>=target){
//两者在同一边时
right=medin;
}else{
//两者在同一边时
left=medin;
}
}
return nums[right]==target?right:-1;
}
}
5、寻找旋转排序数组中的最小值
题目描述:
已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:
- 若旋转
4次,则可以得到[4,5,6,7,0,1,2] - 若旋转
7次,则可以得到[0,1,2,4,5,6,7]
注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]] 。
给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。
你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。
示例 1:
**输入:**nums = [3,4,5,1,2]
**输出:**1
**解释:**原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。
示例 2:
**输入:**nums = [4,5,6,7,0,1,2]
**输出:**0
**解释:**原数组为 [0,1,2,4,5,6,7] ,旋转 4 次得到输入数组。
示例 3:
**输入:**nums = [11,13,15,17]
**输出:**11
**解释:**原数组为 [11,13,15,17] ,旋转 4 次得到输入数组。
提示:
n == nums.length1 <= n <= 5000-5000 <= nums[i] <= 5000nums中的所有整数 互不相同nums原来是一个升序排序的数组,并进行了1至n次旋转
解题思路:
- 把 x 与最后一个数 nums[n−1] 比大小:
- 如果 x>nums[n−1],那么可以推出以下结论:
- nums 一定被分成左右两个递增段;
- 第一段的所有元素均大于第二段的所有元素;
- x 在第一段。
- 最小值在第二段。
- 所以 x 一定在最小值的左边。
- 如果 x≤nums[n−1],那么 x 一定在第二段。(或者 nums 就是递增数组,此时只有一段。)
- x 要么是最小值,要么在最小值右边。
代码如下
Java
class Solution {
public int findMin(int[] nums) {
int n=nums.length;
int left=-1;
int right=n-1;
while(left+1<right){
int mid=(left+right)/2;
int x=nums[mid];
//说明最小值一定在mid左边
if(x<nums[n-1]){
right=mid;
}else{
left=mid;
}
}
return nums[right];
}
}
6、寻找两个正序数组的中位数
题目描述:
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
算法的时间复杂度应该为 O(log (m+n)) 。
示例 1:
**输入:**nums1 = [1,3], nums2 = [2]
**输出:**2.00000
**解释:**合并数组 = [1,2,3] ,中位数 2
示例 2:
**输入:**nums1 = [1,2], nums2 = [3,4]
**输出:**2.50000
**解释:**合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
提示:
nums1.length == mnums2.length == n0 <= m <= 10000 <= n <= 10001 <= m + n <= 2000-106 <= nums1[i], nums2[i] <= 106
解题思路:
- 没看明白推论,后续再刷的时候,再来
- 推理过程请看灵茶山艾府-循序渐进:从双指针到二分
代码如下
Java
class Solution {
public double findMedianSortedArrays(int[] a, int[] b) {
//保证a是较短的那个数组
if(a.length>b.length){
int[]temp=a;
a=b;
b=temp;
}
int m=a.length;
int n=b.length;
int left=-1;
int right=m;
//二分寻找到合适的位置,使得a[i+1]>=b[j]
//保证left,right间有元素
while(left+1<right){
int i=((right-left)>>1)+left;
int j=(m+n+1)/2-i-2;
if(a[i]<=b[j+1]){
left=i;
}else{
right=i;
}
}
int i=left;
int j=(m+n+1)/2-i-2;
int ai=i>=0?a[i]:Integer.MIN_VALUE;
int bj=j>=0?b[j]:Integer.MIN_VALUE;
int ai1=(i+1)<m?a[i+1]:Integer.MAX_VALUE;
int bj1=(j+1)<n?b[j+1]:Integer.MAX_VALUE;
int max=Math.max(ai,bj);
int min=Math.min(ai1,bj1);
return (m+n)%2>0?max:(max+min)/2.0;
}
}