前言
二分查找是一种在有序数组中高效查找特定元素的算法。它在计算机科学领域有着广泛的应用,是算法学习中的重要内容之一。
在实际问题中,我们经常需要在大量的数据中快速定位某个目标元素。二分查找算法凭借其高效的性能和简洁的思想,成为解决这类问题的有力工具。通过对二分查找算法的深入理解和掌握,我们能够更好地应对各种查找相关的挑战,提高程序的运行效率和准确性。
正文
题目(力扣704)
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
makefile
示例 1:
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
makefile
示例 2:
输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1
css
提示:
你可以假设 nums 中的所有元素是不重复的。
n 将在 [1, 10000]之间。
nums 的每个元素都将在 [-9999, 9999]之间。
思路
要对一个所有元素都是不重复的并且是有序的数组进行操作。我们可以想到使用二分查找的方法解决这个算法题,因为二分法的前提条件就是数组为有序数组 并且没有重复元素。如果有重复元素就会使返回小标不唯一。
在想到使用二分法时,便感觉这题稳了。但是总是会因为一些小细节导致无法得到预期结果。
在循环过程中是用left<right
进行判断还是用left<=right
进行判断循环是否继续;在nums[mid]<target
时是让left=mid
还是让left=mid-1
;当nums[mid]>target
时是让right=mid
还是让right=mid-1
。
要怎么解决呢?首先对二分查找的区间进行分类讨论,一般分为两类。
第一类
定义 target 是在一个[left,right]区间里。
第一个困扰:在循环过程中是用left<right
进行判断还是用left<=right
进行判断循环是否继续。
其中唯一的区别就是left=right
是否有意义。在[left,right]区间,left=right
是有意义的,所以选择left<=right
。
第二个困扰:在nums[mid]<target
时是让left=mid
还是让left=mid-1
;当nums[mid]>target
时是让right=mid
还是让right=mid-1
。
在[left,right]区间里,nums[mid]<target
和
nums[mid]>target
里的nums[mid]
一定不等于target
。所以应该选择为让left=mid-1
和让right=mid-1
。
大体代码是这样的
javascript
var search = function(nums, target) {
let left = 0; // 初始化左指针为 0
let right = nums.length - 1; // 初始化右指针为数组的最后一个元素的索引
let mid;
while (left <= right) { // 只要左指针不超过右指针,就继续循环
mid = Math.floor((right - left) / 2) + left; // 计算中间位置(可以防止溢出)
if (nums[mid] === target) { // 如果中间位置的元素值等于目标值
return mid; // 返回中间位置(即找到目标)
} else if (nums[mid] > target) { // 如果中间位置的元素值大于目标值
right = mid - 1; // 将右指针移动到中间位置的左侧
} else { // 如果中间位置的元素值小于目标值
left = mid + 1; // 将左指针移动到中间位置的右侧
}
}
return -1; // 如果循环结束都没有找到目标值,返回 -1 表示未找到
};
第二类
定义 target 是在一个[left,right)区间里。
left=right
在这个区间内并没有意义。所以用left<right
对循环进行判断。
在[left,right)区间nums[mid]<target
时,nums[mid]
一定不等于target
,所以让left=mid-1
,但是因为右边为开区间所以在nums[mid]>target
时,nums[mid]
可能等于target
,所以让right=mid
。
大体代码为
javascript
var search = function(nums, target) {
// 初始化左指针为 0
let left = 0;
// 初始化右指针为数组的长度(注意这里是 nums.length,而不是 nums.length - 1)
let right = nums.length;
let mid;
while (left < right) { // 当左指针小于右指针时,继续循环
mid = Math.floor((right - left) / 2) + left; // 计算中间位置(可以防止溢出)
if (nums[mid] === target) { // 如果中间位置的元素等于目标值
return mid; // 返回中间位置(即找到目标)
} else if (nums[mid] > target) { // 如果中间位置的元素大于目标值
right = mid; // 将右指针移动到中间位置
} else { // 如果中间位置的元素小于目标值
left = mid + 1; // 将左指针移动到中间位置的右侧
}
}
return -1; // 如果循环结束都没有找到目标值,返回 -1 表示未找到
};