题目:给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
提示:
- 你可以假设 nums 中的所有元素是不重复的。
- n 将在 [1, 10000]之间。
- nums 的每个元素都将在 [-9999, 9999]之间。
思路
因为是无重复元素的有序序列(升序),故采用二分查找算法去实现是时间复杂度较好的一种方法,其中无重复元素的目的是为了保证每次的返回的下标都是唯一的。
关键点回忆
-
书写二分法代码的一定要注意你所定义的搜索区间的边界(主流的有左闭右闭,左闭右开),因为不同的边界所对应的代码的判断条件会有细微的差别。
-
在二分查找的过程中一定要保持每次缩小后的查找范围保持 "不变量",即每次缩小后的查找范围都满足你所定义的搜素区间。(保证搜索区间的合法性)
-
二分查找一般while循环判段的条件是left < right 或者是left <= right,会发现始终保持左边界小于右边界,是因为在最坏的时间复杂度的情况下,我们会循环查找到序列中最后一个未被判断的元素时,这是left = right = mid,所以这时若“if”判断后仍未找到目标值,在更新边界值后,不论是right = mid-1,还是left = mid +1,我们发现都是left > right,并且这时所有需要判断的元素都被判断了(即最坏时间复杂度的情况下),所以while循环的判断条件是left < right 或者是left <= right。
c++代码实现方法一
#include <iostream>
#include <vector> //引入vector容器的头文件
using namespace std;
//法一:定义合法的搜索区间为!!!~~左闭右闭时~~
class Solution {
public:
//对升序序列进行目标值搜索
int search_target(vector<int>& nums, int target) {
int left = 0; //左边界
int right = nums.size() - 1; //!!!右边界,保证左闭右闭
// !!!left == right时,区间[left , right]依然合法,并且这时候mid = left = right
while (left <= right) {
//!!!防止int型变量溢出,等同于(left+right)/2,即左边界位置加上左右边界距离的一半就是mid所在的数组下标
int mid = left + (right - left) / 2; //搜索区间的中间位置
//不断缩小搜索区间
//当中间值大于目标中的时候,代表[mid,right]区间内的元素均大于target,故更新左区间的右边界
if (nums[mid] > target)
right = mid - 1; //更新区间为[left , mid-1]
//当中间值小于目标中的时候,代表[left,mid]区间内的元素均小于target,故更新右区间的左边界
else if (nums[mid] < target)
left = mid + 1; //更新区间为[mid + 1 , right]
//目标值等于中间值,直接返回下标
else
return mid;
}
//未找到目标值
return -1;
}
};
int main() {
//测试一下
//声明一个vector容器
vector<int> nums = {2, 3, 5, 6, 21, 23, 29};
Solution s1;
int pos = s1.search_target(nums, 6);
cout << pos;
return 0;
}
c++代码实现方法二
//法二:定义合法的搜索区间为!!!~~左闭右开~~
class Solution {
public:
//对升序序列进行目标值搜索
int search_target(vector<int>& nums, int target) {
int left = 0; //左边界
int right = nums.size(); //!!!右边界,保证左闭右开,不包括right这个边界
//!!!left == right 的时候[left , right]不满足左闭右开,是个非法的区间
while (left < right) {
// !!!防止int型变量溢出,等同于(left+right)/2,即左边界位置加上左右边界距离的一半就是mid所在的数组下标
//搜索区间的中间位置,>>1是算数右移一位,相当于除二,但是比直接除2速度要快
int mid =left +((right - left) >>1);
// 不断缩小搜索区间
//当中间值大于目标值的时候,代表[mid,,right)区间内的元素均大于目标值,故更新左区间的右边界
if (nums[mid] > target)
right = mid; //更新区间为[left, mid),其实就等价于[left, mid-1]
//当中间值小于目标值的时候,代表[left,mid]区间内的元素均小于目标值,故更新右区间的左边界
else if (nums[mid] < target)
left = mid + 1; //更新区间为[mid + 1 , right)
//目标值等于中间值,直接返回下标
else
return mid;
}
//未找到目标值
return -1;
};
};
int main() {
//测试一下
//声明一个vector容器
vector<int> nums = {2, 3, 5, 6, 21, 23, 29};
Solution s1;
int pos = s1.search_target(nums, 21);
cout << pos;
return 0;
}