704. 二分查找 - 力扣(LeetCode)
发布:2021年8月5日12:12:25
问题描述及示例
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/binary-search
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
示例 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
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/binary-search
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
我的题解
主要思路是用两个指针left
和right
分别指向待查找的数组(或者说是子数组)的最左端和最右端,然后用mid
指针始终指向两端的中点位置(left
、right
和mid
存储的都是相应的数组下标)。由于题目说了原数组是有序且是升序的,那么就可以将target
和mid
指针所指向的nums[mid]
作比较。如果target
比nums[mid]
大的话,则说明target
一定不在nums[left] ~ nums[mid]
这个范围内,此时将left
指针更新指向至nums[mid+1]
处;反之,如果target
比nums[mid]
小的话,则说明target
一定不在nums[mid] ~ nums[right]
这个范围内,此时将right
指针更新指向至nums[mid-1]
处。重复这个过程,直到找到与target
相等的数组元素,并返回其下标;若数组中不存在该元素,则返回-1
。
本来想做个动图演示的,但是前几次的经验告诉我,这太麻烦了,而且这个思路用上面的文字讲述应该已经挺明白了,以后回顾的时候应该能看懂,那就先不做图了。
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var search = function(nums, target) {
// 原始数组nums就是一开始的待查找数组,所以将left初始化为0,right初始化为length-1
let left = 0;
let right = nums.length - 1;
let mid;
// 如果left指针还在right指针左边,说明target还有可能在left到right之间被找到
// 要是left和right指向同一个元素,说明要么这个元素就是target,要么就是数组里没有target
while(left <= right) {
// mid = Math.floor(left/2 + right/2);
// 这里为什么要用Math.floor和(right - left) / 2 + left,请看下方详解
mid = Math.floor((right - left) / 2 + left);
// 如果刚好命中target,则将target下标返回,注意题目里说可以认为数组里没有重复元素
if(nums[mid] === target) {
return mid;
}
// 如果target在nums[mid]右边,则移动left指针到mid+1
if(nums[mid] < target) {
left = mid + 1;
} else {
// 如果target在nums[mid]左边,则移动right指针到mid-1
right = mid - 1;
}
}
// 一旦left指针出现在right指针右边,说明target压根就不在数组里
return -1;
};
提交记录
39 / 39 个通过测试用例
状态:通过
执行用时:64 ms, 在所有 JavaScript 提交中击败了97.87%的用户
内存消耗:41.3 MB, 在所有 JavaScript 提交中击败了40.30%的用户
时间:2021/08/05 11:20
为什么要用Math.floor
和(right - left) / 2 + left
?
- 首先是
Math.floor
:下面是我自己做的验证,由下图可知,如果数组下标为非整数,那么可能会出现无法取值的情况。而mid
计算出的结果有可能不是整数,所以用Math.floor
确保数组的下标取整,当然,理论上用Math.ceil
也可以。
- 其次是
(right - left) / 2 + left
:这个式子化简出来其实就是(right+left) / 2
,按理来说,直接用化简出来的式子不是更直观吗?这是为了防止整数溢出,因为nums
数组可能非常长,那么此时right + left
的值就可能非常大,从而超出整数表示范围而导致异常。
有关二分查找,其实我一直有个疑问,那就是为什么非得用两个端点的中间值作为分割点呢?其实一开始我也觉得用中间点是理所当然的,后来不知道从哪里听别人提过这个问题,于是心里也有了相关的疑惑,我开始的想法是:可能这样才能达到最好的平均查找性能吧。今天我特意搜了一下,发现网上有关的讨论不多,看到有个网友说得似乎挺有道理的,详细看下方参考链接。
官方题解
更新:2021年7月29日18:43:21
因为我考虑到著作权归属问题,所以【官方题解】部分我不再粘贴具体的代码了,可到下方的链接中查看。
更新:2021年8月5日12:17:01
【更新结束】
有关参考
更新:2021年8月5日12:27:56
参考:五种js判断是否为整数(转) - 云端观云 - 博客园
参考:javascript数组中数字和非数字下标的区别_不吃鱼的猫-CSDN博客_数组下标可以不是数字吗
更新:2021年8月5日12:50:16
参考:为什么二分查找要取中点作为每次的划分点_Rachelint的博客-CSDN博客
参考:二分查找各种情况大总结_yefengzhichen的博客-CSDN博客
参考:二分查找专题(二)_K_天道酬勤-CSDN博客
更新:2021年8月5日13:31:25
参考:二分查找法的细节(mid值 整数溢出问题)_eddie’s Blog-CSDN博客_二分查找 溢出