思路
核心
二分查找,通过条件的判断,传入整个数组的一半,并且去看这部分数组的中间值是否满足要求,循环此过程,直到找到目标值的下标。
图解
准备数组
准备一个数组,这个数组满足下面两个条件:
- 有序
- 无重复
有序,才能进行“大了还是小了”的条件判断。
无重复,返回的下标才是唯一的。
第一次传入数组
第一次传入数组,没有进行过条件判断,于是传入整个数组
条件判断
由上图所示,mid指向的数字小于target=9
那么,就需要去整个数组的右边部分进行查找
再次传入数组
在这次传入数组中,end的位置没有变化
start的位置变为 原来的mid 的后一个。
mid此时指向target=9,直接返回
终止条件
从上面分析的过程中可以知道,要一次又一次的传入数组,进行处理
那么,什么时候停止传入呢?
在传入数组的过程中,start和end指针会不断靠近。
会不会是当start和end处于某种关系时,循环就停止呢?
考虑这种情况,target=15
再进行一次循环,会变成下面这样
start和end相等了。
这就是中止条件
[start,end]这个区间已经缩到最小,但是还没找到,就是真的找不到了
区间不变
这道题比较难的就是要保证区间的不变性。
区间不变,有两种选择:
- [start,end] 前闭后闭
- [start,end) 前闭后开
对于区间的选择不同,对所有有判断条件的写法是不同的。
在本题中,就是指while、if-else中的判断条件。
[start,end]
while
两个都是闭区间,start=end才有效
判断条件
上面的情况中,我们希望下一次传入数组[3,5]
所以start=mid+1
[start.end)
上面的情况,我们希望下一次传入数组为[0,1]
因为传入的是[ )这种形式
为了传入[0,1],其实需要传入[0,2),因为区间是闭区间,只有这样才能把右端的1取上
所以end=mid-1
1刷
var search = function(nums, target) {
var start = 0;
var end = nums.length;
while(start < end) {
var mid = start + ((end - start) >> 1);
if(nums[mid] > target) {
end = mid;
}else if(nums[mid] < target) {
start = mid + 1;
}else {
return mid;
}
}
return -1;
};
与正解对比
- 区间不变性没考虑
- 跳出while循环的条件
问题点
- js中的除法问题
- 开闭区间的问题
除法的问题
js中,5 / 2 得到的是2.5
而在Java中 5 / 2得到的是2
如果要像Java这样用除法,得带个向下取整的floor方法。
或者用位运算
mid = Math.floor((start + end) / 2);
mid = left + ((right - left) >> 1);
开闭区间
什么时候需要考虑区间的开闭?
答:取数组的子数组进行处理时、确定循环条件时