描述
给定一个 n 个元素有序的(升序)整型数组 nums
和一个目标值 target
,写一个函数搜索 nums
中的 target
,如果目标值存在返回下标,否则返回 -1。
示例 1:
输入:
nums
= [-1,0,3,5,9,12],target
= 9
输出: 4
解释: 9 出现在nums
中并且下标为 4
示例 2:
输入:
nums
= [-1,0,3,5,9,12],target
= 2
输出: -1
解释: 2 不存在nums
中因此返回 -1
情况 一
前提:数组有序(升序)、数组中无重复元素
- 区间定义:
所处理区间的定义是不变量,在二分查找每次处理边界的时候,需要保持一致,也就是 循环不变量。
写二分法,区间的定义一般有两种:[left, right],[left, right)
第一种写法
代码如下:
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1; // 区间形式为 [left, right]
while(left <= right){
int middle = left + ((right - left) >> 1); // 防止溢出,等同 (left + right)/2
if(nums[middle] > target){
right = middle - 1; // target 在区间的左半部分
}else if(nums[middle] < target){
left = middle + 1; // target 在区间右半部分
}else{
return middle; // 数组中找到 target 所在的位置
}
}
return -1; // 未找到 target
}
};
根据以上代码,分析如下:
区间定义采用 [left, right] 形式,所以有:
- 循环的条件为:left <= right;left == right 时成立,如:[1, 1] 这个区间;
- nums[middle] 与 target 不相等时的情况:需要改变区间的左边界或右边界,即:left = middle + 1 或 right = middle - 1;解释:因为 nums[middle] 与 target 已确定不相等,而区间的形式是 [left, right],所以在下次更新 left 或者 right 时,不再需要包含 nums[middle].
第二种写法
代码如下:
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size(); // 区间形式为 [left, right)
while(left < right){
int middle = left + ((right - left) >> 1); // 防止溢出,等同 (left + right)/2
if(nums[middle] > target){
right = middle; // target 在区间的左半部分
}else if(nums[middle] < target){
left = middle + 1; // target 在区间右半部分
}else{
return middle; // 数组中找到 target 所在的位置
}
}
return -1; // 未找到 target
}
};
根据以上代码,分析如下:
区间定义采用 [left, right) 形式,所以有:
- 循环的条件为:left < right;与第一种写法相比,left == right 时不成立,如:[1, 1) 这个区间,左右边界相互矛盾;
- nums[middle] != target 时:需要改变区间的左边界或右边界,与第一种写法相比,区间左边界形式相同,left = middle + 1,而此时,数组中不再包含右边界元素,所以 right = middle;
复杂度分析:
- 时间复杂度:O(log n)
- 空间复杂度:O(1)
情况 二:查找第一个值 等于 target 的元素
前提:数组有序(升序)、数组中存在重复的数据;
输入:
nums
= [1,3,4,5,6,8,8,8,11,18],target
= 8
输出: 5
解释: 8 出现在nums
中并且下标为 5
代码如下:
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1; // 区间形式为 [left, right]
while(left <= right){
int middle = left + ((right - left) >> 1);
if(nums[middle] > target){
right = middle - 1;
}else if(nums[middle] < target){
left = middle + 1;
}else{
if(middle == 0) || (nums[middle - 1] != target) return middle;
else right = middle - 1;
}
}
return -1;
}
};
根据以上代码,分析如下:
区间定义采用 [left,right] 形式,所以有:
- 循环条件和 nums[middle] != target 时的边界处理与情况一的对应部分一致;
- 当 nums[middle] == target 时,因为需要查找第一个值等于 target 的元素,所以再进行判断,确定其是否是第一个符合要求的元素;当 middle == 0 时,是 nums 数组的第一个元素,当然也就是 target 的位置;当 middle 的上一个元素 nums[middle - 1] != target 时,该元素前没有值等于 target 的元素,此时也符合要求;否则需要再进行判断。
按照以上的思想,可以对代码再进行精简,如下:
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1; // 区间形式为 [left, right]
while(left <= right){
int middle = left + ((right - left) >> 1);
if(nums[middle] >= target){
right = middle - 1;
}else if(nums[middle] < target){
left = middle + 1;
}
}
if(left < nums.size() && nums[left] == target) return left;
else return -1;
}
};
情况 三:查找最后一个值等于给定值的元素
前提:数组有序(升序)、数组中存在重复的数据;
思路与情况 二一致
代码如下:
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1; // 区间形式为 [left, right]
while(left <= right){
int middle = left + ((right - left) >> 1);
if(nums[middle] > target){
right = middle - 1;
}else if(nums[middle] < target){
left = middle + 1;
}else{
if(middle == nums.size() -1) || (nums[middle + 1] != target) return middle;
else left = middle + 1;
}
}
return -1;
}
};
情况 四:查找第一个大于等于给定值的元素
代码如下:
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1; // 区间形式为 [left, right]
while(left <= right){
int middle = left + ((right - left) >> 1);
if(nums[middle] >= target){
if((middle == 0) || (nums[middle - 1] < target)) return middle;
else right = middle - 1;
}else{
left = middle + 1;
}
}
return -1;
}
};
情况 五:查找最后一个小于等于给定值的元素
代码如下:
class Solution{
public:
int search(vector<int>& nums, int target){
int left = 0;
int right = nums.size() - 1;
while(left <= right){
int middle = left + ((right - left) >> 1);
if(nums[middle] <= target){
if(middle == nums.size() || nums[middle + 1] > target) return middle;
else left = middle + 1;
}else{
right = middle - 1;
}
}
return -1;
}
};