目录
三、问题:
一、二分查找是什么?
二分查找(Binary Search),也称为折半查找,是一种常用的查找算法。它适用于有序数组或有序列表中查找特定元素的情况。
二分查找的基本思想是将查找区间不断地折半,通过将待查找的元素与区间中间位置的元素进行比较,缩小查找范围,直到找到目标元素或确定目标元素不存在为止。
二、二分查找的不同写法
例题:
这道题目的前提是数组为有序数组,同时题目还强调数组中无重复元素,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的,这些都是使用二分法的前提条件
二分查找涉及了很多的细节,例如到底是 while(left < right)
还是 while(left <= right)
,到底是right = middle
呢,还是要right = middle - 1。
二分法第一种写法(闭区间)
第一种写法,我们定义 target 是在一个在左闭右闭的区间里,也就是[left, right]
因为定义target在[left, right]区间
1.while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <=
2.if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle -1。
代码如下:
class Solution {
public int search(int[] nums, int target) {
int left=0,right=nums.length-1;//这里注意right=nums.length-1,闭区间
while(left<=right){
int mid=left+(right-left)/2;
if(nums[mid]==target) return mid;
else if(nums[mid]<target) left=mid+1;
else if(nums[mid]>target) right=mid-1;
}
return -1;
}
}
二分法第二种写法(左闭右开区间)
如果说定义 target 是在一个在左闭右开的区间里,也就是[left, right)
有如下两点注意:
1、while (left < right),这里使用 < ,因为left == right在区间[left, right)是没有意义的
2、if (nums[middle] > target) right 更新为 middle,因为当前nums[middle]不等于target,去左区间继续寻找,而寻找区间是左闭右开区间,所以right更新为middle,即:下一个查询区间不会去比较nums[middle]
代码如下:
class Solution {
public int search(int[] nums, int target) {
int left = 0, right = nums.length;//注意这里是right=nums.length,因为是左闭右开
while (left < right) {
int mid = left + ((right - left) >> 1);
if (nums[mid] == target)
return mid;
else if (nums[mid] < target)
left = mid + 1;
else if (nums[mid] > target)
right = mid;
}
return -1;
}
}
二分法第三种写法(开区间)
class Solution {
public int search(int[] nums, int target) {
int left = -1, right = nums.length; // 开区间 (left, right)
while (left + 1 < right) { // 区间不为空
int mid = left + (right - left) / 2;
if (nums[mid] < target)
left = mid;
else
right = mid;
}
if (right >= nums.length || nums[right] != target)
return -1;
return right; // 或者 left+1
}
}
对于要求左边界和右边界的题目:
寻找右边界(闭区间)
int getRightBorder(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 if (nums[mid] > target) {
right = mid - 1;
} else if (nums[mid] == target) {
// 收缩左侧边界
left = mid + 1;
}
}
// 检查 right 越界的情况
if (right < 0 || nums[right] != target)
return -1;
return right;
}
寻找右边界(左闭右开区间)
int getRightBorder(int[] nums, int target) {
if (nums.length == 0) return -1;
int left = 0;
int right = nums.length; // 注意
while (left < right) { // 注意
int mid = (left + right) / 2;
if (nums[mid] == target) {
left = mid + 1;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid; // 注意
}
}
if(nums[nums.length-1]==target) return nums.length-1;
// target 比所有数都大
if (left == 0) return -1;
// 类似之前算法的处理方式
return nums[left-1] == target ? left-1 : -1;
}
寻找右区间(开区间)
int getRightBorder(int[] nums, int target) {
int left = -1, right = nums.length; // 开区间 (left, right)
while (left + 1 < right) { // 区间不为空
int mid = left + (right - left) / 2;
if (nums[mid] < target)
left = mid; // 范围缩小到 (mid, right)
else if(nums[mid]>target)
right = mid; // 范围缩小到 (left, mid)
else
left=mid;
}
if(left<0||nums[left]!=target) return -1;
return left; // 或者 left+1
}
寻找左边界(闭区间)
int getLeftBorder(int[] nums, int target) {
int left = 0, right = nums.length - 1;
// 搜索区间为 [left, right]
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
// 搜索区间变为 [mid+1, right]
left = mid + 1;
} else if (nums[mid] > target) {
// 搜索区间变为 [left, mid-1]
right = mid - 1;
} else if (nums[mid] == target) {
// 收缩右侧边界
right = mid - 1;
}
}
// 检查出界情况
if (left >= nums.length || nums[left] != target)
return -1;
return left;
}
寻找左边界(左闭右开区间)
int getLeftBorder(int[] nums, int target) {
if (nums.length == 0) return -1;
int left = 0;
int right = nums.length;
while (left < right) {
int mid = (left + right) / 2;
if (nums[mid] == target) {
right = mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid;
}
}
// target 比所有数都大
if (left == nums.length) return -1;
// 类似之前算法的处理方式
return nums[left] == target ? left : -1;
}
寻找左区间(开区间)
int getLeftBorder(int[] nums, int target) {
int left = -1, right = nums.length; // 开区间 (left, right)
while (left + 1 < right) { // 区间不为空
int mid = left + (right - left) / 2;
if (nums[mid] < target)
left = mid; // 范围缩小到 (mid, right)
else if(nums[mid]>target)
right = mid; // 范围缩小到 (left, mid)
else
right=mid;
}
if(right>=nums.length||nums[right]!=target) return -1;
return right; // 或者 left+1
}
总体代码
开区间
class Solution {
int[] searchRange(int[] nums, int target) {
int leftBorder = getLeftBorder(nums, target);
int rightBorder = getRightBorder(nums, target);
System.out.println(leftBorder+" "+rightBorder);
return new int[]{leftBorder , rightBorder };
}
int getLeftBorder(int[] nums, int target) {
int left = -1, right = nums.length; // 开区间 (left, right)
while (left + 1 < right) { // 区间不为空
int mid = left + (right - left) / 2;
if (nums[mid] < target)
left = mid; // 范围缩小到 (mid, right)
else
right = mid; // 范围缩小到 (left, mid)
}
if(right>=nums.length||nums[right]!=target) return -1;
return right; // 或者 left+1
}
int getRightBorder(int[] nums, int target) {
int left = -1, right = nums.length; // 开区间 (left, right)
while (left + 1 < right) { // 区间不为空
int mid = left + (right - left) / 2;
if (nums[mid] <= target)
left = mid; // 范围缩小到 (mid, right)
else
right = mid; // 范围缩小到 (left, mid)
}
if(left<0||nums[left]!=target) return -1;
return left; // 或者 left+1
}
}
左闭右开区间
class Solution {
int[] searchRange(int[] nums, int target) {
int leftBorder = getLeftBorder(nums, target);
int rightBorder = getRightBorder(nums, target);
System.out.println(leftBorder + " " + rightBorder);
return new int[]{leftBorder, rightBorder};
}
int getLeftBorder(int[] nums, int target) {
if (nums.length == 0) return -1;
int left = 0;
int right = nums.length;
while (left < right) {
int mid = (left + right) / 2;
if (nums[mid] == target) {
right = mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid;
}
}
// target 比所有数都大
if (left == nums.length) return -1;
// 类似之前算法的处理方式
return nums[left] == target ? left : -1;
}
int getRightBorder(int[] nums, int target) {
if (nums.length == 0) return -1;
int left = 0, right = nums.length;
while (left < right) {
int mid = (left + right) / 2;
if (nums[mid] == target) {
left = mid + 1; // 注意
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid;
}
}
if (left == 0) return -1;
return nums[left - 1] == target ? (left - 1) : -1;
}
}
闭区间
class Solution {
int[] searchRange(int[] nums, int target) {
int leftBorder = getLeftBorder(nums, target);
int rightBorder = getRightBorder(nums, target);
System.out.println(leftBorder+" "+rightBorder);
return new int[]{leftBorder , rightBorder };
}
int getLeftBorder(int[] nums, int target) {
int left = 0, right = nums.length - 1;
// 搜索区间为 [left, right]
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
// 搜索区间变为 [mid+1, right]
left = mid + 1;
} else if (nums[mid] > target) {
// 搜索区间变为 [left, mid-1]
right = mid - 1;
} else if (nums[mid] == target) {
// 收缩右侧边界
right = mid - 1;
}
}
// 检查出界情况
if (left >= nums.length || nums[left] != target)
return -1;
return left;
}
int getRightBorder(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 if (nums[mid] > target) {
right = mid - 1;
} else if (nums[mid] == target) {
// 这里改成收缩左侧边界即可
left = mid + 1;
}
}
// 这里改为检查 right 越界的情况,见下图
if (right < 0 || nums[right] != target)
return -1;
return right;
}
}
三、问题:
1.最后返回的到底是right还是left还是其他值?
需要根据循环语句中nums[mid]==target的时候执行的代码来判断
2.在左闭右开的区间中,可不可以要在nums[mid]>target时,要right=mid-1?
不可以,左闭右开即[left,right),right取不到,如果right=mid-1会导致有遗漏。
备注:参考代码随想录二分部分以及几篇文章,进行归纳总结。