题目链接:
给定一个 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
提示:
你可以假设 nums 中的所有元素是不重复的。
n 将在 [1, 10000]之间。
nums 的每个元素都将在 [-9999, 9999]之间。
文章讲解:代码随想录
视频讲解:手把手带你撕出正确的二分法 | 二分查找法 | 二分搜索法 | LeetCode:704. 二分查找_哔哩哔哩_bilibili
正如题目所言,数组是有序数组,且无重复元素,使用二分查找即可解决。而二分查找,区间的划分是非常重要的,区间的划分同时影响到了边界处理。
下面是经典的二分查找的写法,是一种左闭右闭([left, right])的二分查找,也是最普遍的写法。
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var search = function(nums, target) {
let left = 0, right = nums.length - 1;
let mid;
while (left <= right) {
mid = (left + right) >> 1;
if (nums[mid] === target) {
return mid;
} else if (nums[mid] > target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return -1;
};
分析:使用二分查找比双重循环速度要快,时间复杂度为 O(logn),空间复杂度为 O(1)。
下面是另一种二分查找的写法,是左闭右开的二分查找。
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var search = function(nums, target) {
let left = 0, right = nums.length;
while (left < right) {
const mid = left + ((right - 1 - left) >> 1);
if (nums[mid] === target) {
return mid;
} else if (nums[mid] > target) {
right = mid;
} else {
left = mid + 1;
}
}
return -1;
};
分析:左闭右开的二分查找,时间复杂度为 O(logn),空间复杂度为 O(1)。
35. 搜索插入位置
直接用左闭右闭的二分查找解决。
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var searchInsert = function(nums, target) {
let left = 0, right = nums.length - 1;
while (left <= right) {
const mid = (left + right) >> 1;
if (nums[mid] === target) {
return mid;
} else if (nums[mid] > target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return left;
};
分析:左闭右闭的二分查找,时间复杂度为 O(logn),空间复杂度为 O(1)。
34. 在排序数组中查找元素的第一个和最后一个位置
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var searchRange = function(nums, target) {
let left = 0, right = nums.length - 1;
while (left <= right) {
let flag = false;
if (nums[left] < target) {
left++;
flag = true;
}
if (nums[right] > target) {
right--;
flag = true;
}
if (!flag) {
break;
}
}
if (nums[left] !== target || nums[right] !== target) {
return [-1, -1];
}
return [left, right];
};
分析:完整遍历整个数组,时间复杂度为 O(n),空间复杂度为 O(1)。
思路:调用两次二分查找分别找出等于目标值的第1个位置和大于目标值的第1个位置,即可得出答案。
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var searchRange = function(nums, target) {
// flag 为 false 时找左,为 true 时找右
const binarySearch = function (arr, flag) {
let left = 0, right = arr.length - 1;
let res = arr.length;
while (left <= right) {
const mid = left + ((right - left) >> 1);
if (arr[mid] > target || !flag && arr[mid] >= target) {
// 找左时向左边夹逼
right = mid - 1;
res = mid;
} else {
left = mid + 1;
}
}
return res;
};
const start = binarySearch(nums, false);
const end = binarySearch(nums, true) - 1;
if (start > end) {
return [-1, -1];
}
return [start, end];
};
分析:使用了两次二分查找找出目标位置,时间复杂度为 O(logn),空间复杂度为 O(1)。
理解了二分查找的原理,掌握了左闭右闭和左闭右开的两种二分查找的写法,对边界处理也有了新的认知。