算法工具箱之二分查找
二分查找
核心思想:在有序的集合中,通过每次将搜索范围缩小一半来快速定位目标值。
使用前提:
(1)顺序结构:数据结构必须支持通过索引(如数组)或指针(如链表,但链表二分效率低)进行随机访问。
(2)有序性:集合中的元素必须是有序的(升序或降序)。这是二分查找能每次排除一半数据的基础。
下面将二分查找分为标准二分查找;查找第一个等于 target 的元素(适用于有重复元素的数组);查找最后一个等于 target 的元素;查找第一个大于等于 target 的元素(可用于插入位置);查找第一个大于 target 的元素为大家讲清楚二分查找这一种算法。
(1)标准二分查找:(时间复杂度:O(log n),空间复杂度:O(1)。)
class Solution {
public:int search(vector<int>& nums, int target) {int left = 0;int right = nums.size() - 1;while(left <= right){int mid = left + (right - left)/2;if(nums[mid] > target){right = mid-1;}else if(nums[mid] < target){left = mid+1;}else{return mid;}}return -1;}
};这一题是标准二分查找的例题,我们需要先定义left和right指向数组的两端,再定义mid变量来求出每次数组的中间元素的下标。因为数组的元素是递增的,所以当数组中间的元素大于目标值时,我们往左收缩,反之,我们向右收缩。当left与right错开时,整个数组中就没有这个元素,返回-1。
(2)查找第一个与最后一个等于target的位置
class Solution {
public:vector<int> searchRange(vector<int>& nums, int target) {if(nums.size() == 0){return {-1,-1};}   int begin = 0;int left = 0;int right = nums.size() - 1;while(left < right){int mid = left + (right - left) /2;if(nums[mid] < target){left = mid + 1;}else{right = mid;}}if(nums[left] != target){return {-1,-1};}else{begin = left;}left = 0;right = nums.size() - 1;while(left < right){int mid = left + (right - left + 1)/2;if(nums[mid] > target){right = mid - 1;}else{left = mid;}}return {begin,right};}
};算法思路:⽤的还是⼆分思想,就是根据数据的性质,在某种判断条件下将区间⼀分为⼆,然后舍去其中⼀个 区间,然后再另⼀个区间内查找。
算法步骤:
查找左边界:找到第一个等于target的值。
当nums[mid] < target时,说明目标在右侧,所以让left = mid + 1;当nums[mid] >= target时,说明目标在左侧或者当前位置,所以让right = mid。当left == right程序循环终止。若nums[left] != target时,即不存在target值,返回{-1,-1},若存在则把left值储存在begin中。
查找右边界:找到最后一个等于target的值
也是类似的道理,不过mid计算时要向上取整,避免死循环。
(3)查找第一个大于等于target值的元素。
class Solution {
public:int searchInsert(vector<int>& nums, int target) {int left = 0;int right = nums.size() - 1;while(left < right){int mid = left + (right - left)/2;if(nums[mid] < target){left = mid + 1;}else{right = mid;}}if(nums[left] < target){return right + 1;}return right;}
};核心思想:如果找到目标值直接返回right值,如果没找到则返回right + 1。
使用的方法与前面的二分查找类似。
时间复杂度为O(log n),空间复杂度O(1)。
