二分查找
二分查找也常被称为二分法或者折半查找(Binary Search),每次查找时通过将待查找区间分成两部分并只取一部分继续查找,将查找的复杂度大大减少。对于一个长度为 O(n) 的数组,二分查找的时间复杂度为 O(log n)。二分查找也可以看作双指针的一种特殊情况。双指针类型指针通常是一步一步移动的,而在二分查找里指针每次移动半个区间长度。通常题目给定顺序存储结构我们可以联想到二分查找。
下面给出了二分查找的算法框架,需要注意的部分就在于左右边界的初始定义、while循环执行的条件以及判断后左右边界如何移动。
二分查找算法框架
public int binarySearch(int[] nums, int target) {
// 定义左右边界
int left = 0, right = ...;
// 遍历
while(...) {
// 取区间中点位置 使用 left + (right - left) / 2 比使用 (left + right) / 2 更好,可以避免溢出
int mid = left + (right - left) / 2;
// 如果找到目标值,则执行...
if (nums[mid] == target) {
...
} else if (nums[mid] < target) {
// 如果中点值小于目标值,则缩小区间,移动左边界
left = ...
} else if (nums[mid] > target) {
// 如果中点值大于目标值,则缩小区间,移动右边界
right = ...
}
}
// 返回值
return ...;
}
二分查找一个数的算法框架
// 搜索区间:左闭右闭区间
public int binarySearch(int[] nums, int target) {
// 定义左边界为数组头
int left = 0;
// 定义右边界为数组长度减一
int right = nums.length - 1;
// 当左边界小于等于右边界时执行循环 左闭右闭区间 [left, right]
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;
}
// 找不到目标值,返回-1
return -1;
}
寻找左侧边界的二分查找算法框架
// 搜索区间:左闭右开区间
public int left_bound(int[] nums, int target) {
// 如果数组为空,直接返回-1
if (nums.length == 0)
return -1;
// 定义左边界起始为0
int left = 0;
// 定义右边界起始为数组长度
int right = nums.length;
// 当左边界小于右边界时执行循环 搜索区间是 [left, right) 左闭右开
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 比所有数都大,返回-1
if (left == nums.length)
return -1;
// 如果左边界值等于目标值则返回左边界位置,否则返回-1
return nums[left] == target ? left : -1;
}
寻找右侧边界的二分查找算法框架
// 搜索区间:左闭右开区间
public int right_bound(int[] nums, int target) {
// 如果数组为空,直接返回-1
if (nums.length == 0)
return -1;
// 定义左边界起始为0
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;
}
// 左边界还在初始位置,返回-1
if (left == 0)
return -1;
// 如果左边界左边一个元素值等于目标值则返回左边界左边一个元素的位置,否则返回-1
return nums[left - 1] == target ? (left-1) : -1;
}
以上三种框架的左闭右闭区间算法
// 二分查找一个数的算法框架
public int binary_search(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) {
// 直接返回
return mid;
}
}
// 直接返回
return -1;
}
// 寻找左侧边界的二分查找算法框架
public int left_bound(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) {
// 别返回,锁定左侧边界
right = mid - 1;
}
}
// 最后要检查 left 越界的情况
if (left >= nums.length || nums[left] != target)
return -1;
return left;
}
// 寻找右侧边界的二分查找算法框架
public int right_bound(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;
}