(一)问题描述
leetcode题目链接https://leetcode.cn/problems/binary-search/
给定一个 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]之间。
(二)关键词提取
- 查找
- 有序数组
- 无重复元素
满足以上条件的问题可以考虑使用二分查找(也叫二分法、折半查找)。
该方法可以简单理解成:取有序数组中间的元素与目标值进行比较,如果恰好中间值和目标值相等,则直接输出中间元素的索引,计算结束。对于问题中的升序数组,如果目标值大于中间值,则取数组中间元素右侧的部分,反之则取左侧部分,用这部分的中间元素与目标值再次比较。循环以上操作,直到找出与目标元素相等的元素,或者排除所有可能元素,计算终止。
(三)易错点
- while终止条件如何设置,应该是left<=right,还是left==right(left和right是当前计算区间的左端点和右端点索引值)
- 参与计算的部分变化时,right等于middle还是middle-1/left等于middle还是middle+1(middle是当前计算区间的中间位置元素索引)
(四)解题思路
回避以上易错点的关键在于:确定每次计算的区间。一般采用左闭右闭[left,right](即每次两端的元素都参与计算)或者左闭右开[left right)(即每次左端元素参与计算,而右端元素不参与计算),至于为啥不经常用左开右闭(left,right],这个一会儿再说。
1. 左闭右闭方式
(1)while循环的条件采用left<=right。这一点看只剩下一个元素时区间是否符合闭区间的定义。
(2)每次计算区间变化时,right=middle-1/left=middle+1。因为两侧元素都参与计算,而中间位置元素刚刚已经比较过了不是要找的元素,所以要把中间元素拿掉。
伪代码:
BinarySearch ( A[0..n-1], K )
//左闭右闭的方式实现非递归的折半查找
//输入:一个升序数组 A[0..n-1] 和一个查找键K
//输出:一个数组元素的下标,该元素等于K;如果没有这样一个元素,则返回-1
left←0;right←n-1
while left<=right do
m←⌊(left+right)/2⌋(这个符号是向下取整符号)
if K=A[m] return m
else if K<A[m] right←m-1
else left←m+1
return -1
2. 左闭右开方式
(1)起始时right不是length-1啦!右端点不参与计算,所以right应该是length!否则最后一个元素就没办法参与计算啦!这也就是为啥不经常用左开右闭,毕竟最后一个元素再“后面一个”更好表示。
(2)while循环的条件采用left<right。还是看只剩下一个元素时区间是否符合闭区间的定义。
(3)每次计算区间变化时,right=middle/left=middle+1。中间位置元素刚刚已经比较过了不是要找的元素,所以要把中间元素拿掉。计算区间取左侧一半时,右端点移动。右端点是不参与计算的,所以要正好移动到刚刚middle的位置上,如果取middle-1,那middle-1就不参与计算了,少了个元素。
伪代码:
BinarySearch ( A[0..n-1], K )
//左闭右开的方式实现非递归的折半查找
//输入:一个升序数组 A[0..n-1] 和一个查找键K
//输出:一个数组元素的下标,该元素等于K;如果没有这样一个元素,则返回-1
left←0;right←n
while left<right do
m←⌊(left+right)/2⌋(这个符号是向下取整符号)
if K=A[m] return m
else if K<A[m] right←m
else left←m+1
return -1
(五)题解里的小知识
- 开头有一段判断,即如果target比数组的最小值小,比最大值大,那么就不用比了,肯定找不到,避免了多次循环计算。
if (target < nums[0] || target > nums[nums.length - 1]) {
return -1;
}
2. 在计算下个计算区间的middle值时,采用的不是(left+right)/2的方法,而是有符号右移的方式
int mid = left + ((right - left) >> 1);
在java语言里,整数除法是向下取整的,即直接舍掉小数部分,无论小数部分多大都舍掉,没有进1这一说。这里(right-left)>>1的写法是有符号右移一位,其实也是除以2的意思,并且也相当于向下取整。
有关移位运算符的解释https://blog.csdn.net/qq_53132084/article/details/131152100