一、题目
LeetCode-算法入门-34. 在排序数组中查找元素的第一个和最后一个位置
地址:https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/
难度:中等
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
进阶:
你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?
示例 1:
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例 2:
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
示例 3:
输入:nums = [], target = 0
输出:[-1,-1]
提示:
0 <= nums.length <= 10^5
-10^9 <= nums[i] <= 10^9
nums 是一个非递减数组
-10^9 <= target <= 10^9
二、解题思路
题目说了给定一个有序数组,并且想让你使用时间复杂度为 O(log n) 的算法求解。很明显该题要使用二分查找进行求解,但问题就在于如何找到target在有序数组中的上限和下限。
假设 target = 7,nums = [7,7,7,7,7]。
那么思考一个问题,此时使用二分查找,找到的7是哪个位置的7?
在这里,我们采用左闭右开的区间。
对于7的lower_bound我们再查找过程,我们要使得left向左移,找到7的下限。
int lower_bound(vector& nums,int target){
int left = 0,right = nums.size(),mid;
while(left < right){
mid = left + (right - left)/2;
//使得left向左移
if(nums[mid] >= target){
right = mid;
}else{
left = mid + 1;
}
}
return left;
}
对于7的upper_bound我们再查找过程,我们要使得left向右移,找到7的上限。
int upper_bound(vector& nums,int target){
int left = 0,right = nums.size(),mid;
while(left < right){
mid = left + (right - left)/2;
//使得left向右移
if(nums[mid] > target){
right = mid;
}else{
left = mid + 1;
}
}
return left - 1;//-1上限才不会越界
}
这样找到的上下限是存在问题的,我们需要进行检验。
int lower = lower_bound(nums,target);
int upper = upper_bound(nums,target);
if(lower == nums.size() || nums[lower] != target){
return vector{-1,-1};
}
return vector{lower,upper};
三、实现过程
c++
class Solution {
public:
vector searchRange(vector& nums, int target) {
if(nums.size() == 0) return vector{-1,-1};
int lower = lower_bound(nums,target);
int upper = upper_bound(nums,target);
if(lower == nums.size() || nums[lower] != target){
return vector{-1,-1};
}
return vector{lower,upper};
}
int lower_bound(vector& nums,int target){
int left = 0,right = nums.size(),mid;
while(left < right){
mid = left + (right - left)/2;
if(nums[mid] >= target){
right = mid;
}else{
left = mid + 1;
}
}
return left;
}
int upper_bound(vector& nums,int target){
int left = 0,right = nums.size(),mid;
while(left < right){
mid = left + (right - left)/2;
if(nums[mid] > target){
right = mid;
}else{
left = mid + 1;
}
}
return left - 1;
}
};
PHP
class Solution {
/**
* @param Integer[] $nums
* @param Integer $target
* @return Integer[]
*/
function searchRange($nums, $target) {
if(empty($nums)){
return [-1,-1];
}
$lower = $this->lower_bound($nums, $target);
$upper = $this->upper_bound($nums, $target);
if($lower == count($nums) || $nums[$lower] != $target){
return [-1,-1];
}
return [$lower,$upper];
}
function lower_bound($nums,$target){
$left = 0;
$right = count($nums);
while($left < $right){
$mid = (int)($left + ($right-$left)/2);
if($nums[$mid] >= $target){
$right = $mid;
}else{
$left = $mid + 1;
}
}
return $left;
}
function upper_bound($nums,$target){
$left = 0;
$right = count($nums);
while($left < $right){
$mid = (int)($left + ($right-$left)/2);
if($nums[$mid] > $target){
$right = $mid;
}else{
$left = $mid + 1;
}
}
return $left - 1;
}
}
JavaScript
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var searchRange = function(nums, target) {
if(nums.length == 0){
return [-1,-1];
}
let lower = lower_bound(nums,target);
let upper = upper_bound(nums,target);
if(lower == nums.length || nums[lower] != target){
return [-1,-1];
}
return [lower,upper];
};
function lower_bound(nums,target){
let left = 0,right = nums.length,mid;
while(left < right){
mid = Math.floor(left + (right - left)/2);
if(nums[mid] >= target){
right = mid;
}else{
left = mid + 1;
}
}
return left;
};
function upper_bound(nums,target){
let left = 0,right = nums.length,mid;
while(left < right){
mid = Math.floor(left + (right - left)/2);
if(nums[mid] > target){
right = mid;
}else{
left = mid + 1;
}
}
return left - 1;
};
四、小结
二分查找时区间的左右端取开区间还是闭区间在绝大多数时候都可以,如何设置合适的条件在区间里查找结果显得尤为重要。
二分查找也可以看作双指针的一种特殊情况。
双指针类型的题,指针通常是一步一步移动的,而在二分查找里,指针每次移动半个区间长度。