参考连接
二分法可以达到 O(log n)的时间复杂度
一般而言,当一个题目出现以下特性时,你就应该立即联想到它可能需要使用二分查找:
- 待查找的数组 有序或者部分有序
- 要求时间复杂度低于O(n),或者直接要求时间复杂度为O(log n)
mid 取值
可以保证两数相加除2永远不会发生 越界问题
还可以保证在查找区间 长度为偶数 时,二分过程中其mid始终指向中间偏左的元素,向下取整
int mid=left+(right-left)/2;
边界问题
参考链接:
二分法的边界问题一般和left和right的取值有关,一般可以选择 左闭右闭 和 左闭右开
左闭右闭
left 指向数组中第一个元素(0),right 指向数组中最后一个元素(len-1)
考虑while循环中的条件,因为left和right都在数组范围内,因此 left<=right 也是需要进入循环并判断
循环条件中包含了 left == right的情况,则必须在每次循环中改变 left 和 right的指向,以防止进入死循环
public int search(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left <= right) {
// 用右移操作替代除法提升性能
int mid = left + ((right - left) >> 1);
if (nums[mid] == target){
// 循环终止的条件1 :找到目标值
return mid;
}else if (nums[mid] > target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
// 循环终止的条件2 :left > right
return -1;
}
左闭右开
情况一
left指向数组中第一个元素(0),right 指向数组的长度(len)
int left=0;
int right=arr.length;
while (left<right){
.....
}
在循环过程中选择 向下取整,使用了左闭右开的方法,向上取整可能出现越界问题
情况2
查找左边界:数组有序,但包含重复元素 或者 数组部分有序,且不包含重复元素
要寻找左边界,搜索范围就需要从右边开始,不断往左边收缩
public int search(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid + 1;
}
}
// 当target比nums中所有元素都大时,会存在以下情况使得索引越界
if (left >= nums.length || nums[left] != target)
return -1;
return left;
}
要寻找左边界
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] > target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
// 检查 right 越界
if (right < 0 || nums[right] != target)
return -1;
return right;
情况三
数组部分有序且包含重复元素的
与上面唯一区别就在于对右侧值的收缩更加保守
这种收缩方式可以有效地防止一下子跳过目标边界导致搜索区域的遗漏
public int search(int[] nums, int target) {
int left = 0;
int 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 {
right--;
}
}
return nums[left] == target ? left : -1;
}