二分查找细节问题
二分查找细节问题
1. 基本的二分查找
1.1 两种实现
// 情况一:right = nums.length
int binary_search(int[] nums, int target) {
// 不需要在这里判断,长度为 0,不能进入循环,且不能通过最后的判断
// if (nums.length == 0) return -1;
// 查找区间为 [left, right)
int left = 0, right = nums.length;
while (left < right) {
// 避免了 left + right 可能溢出的情况
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
return mid;
} else if (nums[mid] < target) {
// 因为查找区间左侧是闭区间,left 指向的元素能被搜索,所以赋值为 mid + 1
left = mid + 1;
} else if (nums[mid] > target) {
// 因为查找区间右侧是开区间,right 指向的元素不能被搜索,所以不能赋值为 mid - 1
right = mid;
}
}
// 因为循环的结束条件是 left = right,它们指向的元素未被判断,所以还要再判断这个遗漏的元素
return nums[left] == target ? left : -1;
}
// 情况二:right = nums.length - 1
int binary_search(int[] nums, int target) {
// 查找区间为 [left, right]
int left = 0, right = nums.length - 1;
while (left <= right) {
// 避免了 left + right 可能溢出的情况
int mid = left + (right - left) / 2;
if (nums[mid] == targer) {
return mid;
} else if (nums[mid] > target) {
// 因为查找区间右侧是闭区间,right 指向的元素能被搜索,所以赋值为 mid - 1
right = mid - 1;
} else if (nums[mid] < target) {
// 因为查找区间左侧是闭区间,left 指向的元素能被搜索,所以赋值为 mid + 1
left = mid + 1;
}
}
// 不存在遗漏的情况
return -1;
}
2. 寻找左侧边界的二分查找
遗漏: 指第一次 mid 定位的那个元素刚好就是 target 元素所求边界的特殊情况
2.1 两种实现
// 情况一:right = nums.length
public int searchLeft(int[] nums, int target) {
// 查找区间为 [left, right)
int left = 0, right = nums.length;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
// 因为要查找左边的临界值,所以要将查找区间的右侧缩小,右侧是开区间,right 指向的值不能被判断,产生遗漏
right = mid;
} else if (nums[mid] < target) {
// 因为查找区间左侧是闭区间,left 指向的元素能被搜索,所以赋值为 mid + 1
left = mid + 1;
} else {
// 因为查找区间右侧是开区间,right 指向的元素不能被搜索,所以不能赋值为 mid - 1
right = mid;
}
}
// 1. 越界。target 比所有的值都要大
// 2. 没有越界。因为循环的结束条件是 left = right,这个遗漏的元素就是 left 指向的元素
if (left == nums.length || nums[left] != target) {
return -1;
}
return left;
}
// 情况二:right = nums.length - 1
public int searchLeft(int[] nums, int target) {
// 查找区间为 [left, right]
int left=0, right=nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
// 因为查找区间右侧是闭区间,right 指向的元素能被搜索,所以赋值为 mid - 1。遗漏的元素为 right + 1
right = mid - 1;
} else if (nums[mid] < target) {
// 因为查找区间左侧是闭区间,left 指向的元素能被搜索,所以赋值为 mid + 1
left = mid + 1;
} else {
// 因为查找区间右侧是闭区间,right 指向的元素能被搜索,所以赋值为 mid - 1
right = mid - 1;
}
}
// 1. 越界。target 比所有的值都要大
// 2. 没有越界。因为循环的结束条件是 left = right + 1,这个遗漏的元素就是 left 指向的元素
if (left == nums.length || nums[left] != target) {
return -1;
}
return left;
}
3. 寻找右侧边界的二分查找
3.1 两种实现
// 情况一:right = nums.length
public int searchRight(int[] nums, int target) {
// 查找区间为 [left, right)
int left=0, right=nums.length;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
// 因为要查找右边的临界值,所以要将查找区间的左侧缩小,左侧是闭区间,
// left 指向的值能被判断,所以指向 mid + 1,同时产生遗漏
left= mid + 1;
} else if (nums[mid] < target) {
// 因为查找区间左侧是闭区间,left 指向的元素能被搜索,所以赋值为 mid + 1
left = mid + 1;
} else {
// 因为查找区间右侧是开区间,right 指向的元素不能被搜索,所以不能赋值为 mid - 1
right = mid;
}
}
// 处理越界情况和遗漏判断
if (right == 0 || nums[right - 1] != target) {
return -1;
}
return right - 1;
}
// 情况二:right = nums.length - 1
public int searchRight(int[] nums, int target) {
// 查找区间为 [left, right]
int left=0, right=nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
// 因为要查找右边的临界值,所以要将查找区间的左侧缩小,左侧是闭区间,
// left 指向的值能被判断,所以指向 mid + 1,同时产生遗漏
left= mid + 1;
} else if (nums[mid] < target) {
// 因为查找区间左侧是闭区间,left 指向的元素能被搜索,所以赋值为 mid + 1
left = mid + 1;
} else {
// 因为查找区间右侧是闭区间,right 指向的元素能被搜索,所以赋值为 mid - 1
right = mid - 1;
}
}
// 处理越界情况和遗漏情况
if (right < 0 || nums[right] != target) {
return -1;
}
return right;
}
参考:二分查找详解

浙公网安备 33010602011771号