二分法查找概述
二分法查找,也称为折半法,是一种在有序数组中查找特定元素的搜索算法。
步骤
主要分为以下几步:
- 根据左侧(left)右侧(right)索引计算中间【mid = left + (right - left) / 2 这样计算是为了防止索引相加大小溢出】索引值,比较中间(mid)索引的数值是否跟目标值target一致
- 如果一致,则返回mid
- 如果不一致,分两种情况,如果mid索引的数值小于目标元素target,则target可能位于mid索引的右侧,则 left 索引调整(left = mid + 1);如果mid索引的数值大于目标元素target,则target可能位于mid索引的左侧,则right索引调整(right = mid - 1),继续循环判断
- 继续循环的条件为 left <= right
代码实现
标准二分法的代码实现,时间复杂度是 log2(n)
// 标准二分法
public int binarySearch(int[] nums, int target){
int left = 0, 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 {
right = mid - 1;
}
}
return -1;
}
二分法的变形
这里我所说的变形只是换个写法,不要误解
变形1,查找比目标元素大的第一个元素的位置
变形:去除等于target元素的判断,left的变更包含等于的情况,所以查询res是第一个大于target元素的位置(如果包含)
public int binarySearch2(int[] nums, int target){
int left = 0, right = nums.length - 1;
int res = -1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] > target) {
right = mid - 1;
res = mid;
} else {
left = mid + 1;
}
}
// 处理包含比target大的元素
if (res != -1 && res > 0 && nums[res] > target) {
return res;
}
return -1;
}
变形2,数组有序,但是有重复元素,找到目标元素的开始位置
有序数组中存在重复元素,查找匹配元素的第一个位置
public int binarySearch3(int[] nums, int target){
int left = 0, right = nums.length - 1;
int res = -1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] >= target) {
right = mid - 1;
res = mid;
} else {
left = mid + 1;
}
}
if (res != -1 && nums[res] == target) {
return res;
}
return -1;
}
综合
上述两个变形主要来源于题目,34. 在排序数组中查找元素的第一个和最后一个位置
通过二分法的变形,查找到目标元素第一次出现的位置以及比目标元素大的第一元素出现的位置
public int[] searchRange(int[] nums, int target) {
int leftIdx = binarySearch(nums, target, true);
int rightIdx = binarySearch(nums, target, false) - 1;
if (leftIdx <= rightIdx && rightIdx < nums.length &&
nums[leftIdx] == target && nums[rightIdx] == target) {
return new int[]{leftIdx, rightIdx};
}
return new int[]{-1, -1};
}
public int binarySearch(int[] nums, int target, boolean lower) {
int left = 0, right = nums.length - 1, ans = nums.length;
while (left <= right) {
int mid = (left + right) / 2;
if (nums[mid] > target || (lower && nums[mid] >= target)) {
right = mid - 1;
ans = mid;
} else {
left = mid + 1;
}
}
return ans;
}