二分查找算法
二分查找是一种在有序数组中查找特定元素的算法。其基本思想是将目标值与数组中间的元素进行比较:
- 如果目标值等于中间元素,查找成功,返回该元素的索引。
- 如果目标值小于中间元素,说明目标值在数组的左半部分,更新搜索区间为左半部分。
- 如果目标值大于中间元素,说明目标值在数组的右半部分,更新搜索区间为右半部分。
这个过程将重复进行,直到找到目标值或搜索区间为空。
前提条件
- 数组必须是有序的。
- 数组中元素不重复。
边界条件处理
文章中提到了两种不同的区间定义,这影响了边界条件的处理方式:
-
左闭右闭区间 [left, right]
while (left <= right)
循环条件,因为当left
和right
相等时,区间仍然有效。- 如果
nums[middle] > target
,则right = middle - 1
,排除中间元素。
-
左闭右开区间 [left, right)
while (left < right)
循环条件,因为当left
和right
相等时,区间无效。- 如果
nums[middle] > target
,则right = middle
,不包括中间元素。
代码实现
文章提供了两种写法的代码实现,包括详细的注释,解释了每一步操作的原因。
时间与空间复杂度
- 时间复杂度:O(log n),因为每次比较将搜索空间减半。
- 空间复杂度:O(1),因为除了输入数组外,只需要常数级别的额外空间。
这段Java代码提供了两种实现二分查找算法的版本,一种是左闭右闭区间的实现,另一种是左闭右开区间的实现。下面是对这两个版本的通俗详细解释:
class Solution {
public int search(int[] nums, int target) {
// 避免当 target 小于nums[0] nums[nums.length - 1]时多次循环运算
if (target < nums[0] || target > nums[nums.length - 1]) {
return -1;
}
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + ((right - left) >> 1);
if (nums[mid] == target) {
return mid;
}
else if (nums[mid] < target) {
left = mid + 1;
}
else { // nums[mid] > target
right = mid - 1;
}
}
// 未找到目标值
return -1;
}
}
### 版本一:左闭右闭区间 [left, right]
在这个版本中,初始时,搜索区间是 `[left, right]`,表示包括数组的第 `left` 项和第 `right` 项。数组的索引从0开始,所以 `nums.length - 1` 是数组最后一个元素的索引。
1. 首先检查目标值 `target` 是否在数组的边界内。如果不在,直接返回-1,表示未找到。
2. 初始化左右指针 `left` 和 `right`,分别指向数组的起始和结束。
3. 在 `while` 循环中,使用位运算符 `>> 1` 计算中间索引 `mid`,这是一种高效的除以2的操作,同时防止可能的整数溢出。
4. 比较 `nums[mid]` 和 `target`:
- 如果相等,返回 `mid`,表示找到了目标值。
- 如果 `nums[mid]` 小于 `target`,则将 `left` 更新为 `mid + 1`,排除左边的元素。
- 如果 `nums[mid]` 大于 `target`,则将 `right` 更新为 `mid - 1`,排除右边的元素。
5. 如果循环结束仍未找到目标值,返回-1。
class Solution {
public int search(int[] nums, int target) {
int left = 0, 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 { // nums[mid] > target
right = mid;
}
}
// 未找到目标值
return -1;
}
}
### 版本二:左闭右开区间 [left, right)
这个版本与版本一类似,但有一些关键的区别:
- 初始搜索区间是 `[left, right)`,表示不包括数组的第 `right` 项。
- 循环条件使用 `<` 而不是 `<=`,因为 `right` 索引在结束时是不包括在内的(右开区间)。
- 更新 `right` 时,直接设置为 `mid`,因为 `right` 已经是开区间,不需要减1。
### 共同点
- 两种版本都使用了 `while` 循环和位运算来避免整数溢出。
- 两种版本在找到目标值时返回相应的索引,在未找到时返回-1。
### 差异
- 区间的定义不同,导致循环条件和更新边界索引的方式不同。
这些代码示例清晰地展示了二分查找算法的实现,并考虑了整数溢出的问题,通过使用位运算符 `>>` 来安全地计算中间索引。同时,通过预检查目标值是否在数组边界内,避免了无意义的循环运算。