参考二分查找。
二分查找算法的编写框架大致如下:
int binarySearch(int[] nums, int target) {
int left = 0, right = ...;
while(...) {
int mid = (right + left) / 2;
if (nums[mid] == target) {
...
} else if (nums[mid] < target) {
left = ...
} else if (nums[mid] > target) { // 写成else更简化
right = ...
}
}
return ...;
}
计算mid
时有技巧可以防止溢出,写成mid = left + (right - left) / 2
。
这个场景是最简单的,也是我们最熟悉的。搜索一个数,如果存在则返回其下标,否则返回 -1。
n
个元素,下标范围为[0 : n-1]
,要查询的元素为key
。min = 0, max = n-1
。这里提出一个搜索区间的定义,此时算法的搜索区间就是闭区间[min, max]
,搜索区间的特点决定了min, max
的移动方式。while(min <= max)
,计算中间元素mid = (left + right) / 2
if A[mid] > key, then max = mid - 1
if A[mid] < key, then min = mid + 1
min, max
移动时都是需要加一或者减一nums[mid] = target
,要么为left = right + 1
。int binarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1; // 注意
while(left <= right) { // 注意
int mid = (right + left) / 2;
if(nums[mid] == target)
return mid;
else if (nums[mid] < target)
left = mid + 1; // 注意
else if (nums[mid] > target)
right = mid - 1; // 注意
}
return -1;
}
min, max
移动方法的话,最基本的二分查找不可以直接修改为min=0. max=n, while(min < max)
这种写法,可以举个例子,nums = [1, 2, 3], target = 1
,min=0, max=3, mid=1, nums[1] > target
,移动max
之后min = max
直接结束,所以这种写法是错的。min = 0, max = n, while(min < max)
,那么移动时left = mid + 1
不用动,但是right = mid
!min = 0, max = n
。那么此时算法的搜索区间为左闭右开的[left, right)
。while(min < max)
,计算中间元素mid = (left + right) / 2
if A[mid] > key, then max = mid
!!!!!if A[mid] < key, then min = mid + 1
nums[mid] = target
,要么为left = right
。int binarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length; // 注意
while(left < right) { // 注意
int mid = (right + left) / 2;
if(nums[mid] == target)
return mid;
else if (nums[mid] < target)
left = mid + 1;
else if (nums[mid] > target)
right = mid; // 注意
}
return -1;
}
也就是说,初始化的min, max
决定了搜索区间的特性,进而决定了算法中循环的判断条件以及min, max
的移动方式。(即,修改min. max
的初始化条件的话,只需要修改while
内的判断条件以及min, max
的移动方式即可)
参考二分查找的变种:其中链接中的的2.5, 2.6
写的有问题,没有考虑min
可能越界的情况,正确写法参考下面的5, 6
。
关于二分查找,如果条件稍微变换一下,比如:数组之中的数据可能可以重复,要求返回匹配的数据的最小(或最大)的下标;更进一步, 需要找出数组中第一个大于key的元素(也就是最小的大于key的元素的)下标,等等。 这些,虽然只有一点点的变化,实现的时候确实要更加的细心。
二分查找的变种和二分查找原理一样,主要就是变换判断条件(也就是边界条件)。
int binarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while(left <= right) {
int mid = (right + left) / 2;
if(nums[mid] == target)
right = mid - 1; // 算法能够搜索第一个key的关键所在!!
else if (nums[mid] < target)
left = mid + 1;
else if (nums[mid] > target)
right = mid - 1;
}
if(left < nums.leghth && nums[left] == target) {
return left;
}
return -1; // 还有可能没有找到!
}
left = 0, right = n - 1
,循环条件中为left <= right
,因此循环终止条件为left = right + 1
,所以left
的取值范围为[0, n]
,所以最后需要判断left < nums.length?
。mid
为final
,那么下一步搜索区间会变成[left, final - 1]
,因为这个区间里面全是小于 key 的元素,所以最后结束时,left = final, right = final - 1
,left
整好指到了第一个与 key 相等的元素位置。left = right = final
,下一步left = final, right = final - 1
,不满足循环条件终止。此时left
同样指向了第一个也是唯一一个与 key 相等的元素位置。分析的时候,将nums[mid]
与target
的关系分三部分进行讨论,分析完可以合并:
int binarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while(left <= right) {
int mid = (right + left) / 2;
if(nums[mid] >= target) // 合并
right = mid - 1;
else if (nums[mid] < target)
left = mid + 1;
}
if(left < nums.leghth && nums[left] == target) {
return left;
}
return -1;
}
int binarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while(left <= right) {
int mid = (right + left) / 2;
if(nums[mid] <= target)
left = mid + 1;
else if (nums[mid] > target)
right = mid - 1;
}
if(right >= 0 && nums[right] == target) {
return right;
}
return -1;
}
int binarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while(left <= right) {
int mid = (right + left) / 2;
if(nums[mid] <= target)
left = mid + 1;
else if (nums[mid] > target)
right = mid - 1;
}
if(right >= 0 && nums[right] <= target) {
return right;
}
return -1; // 还有可能没有找到!
}
其实最后可以直接写成reutrn right
,因为对于特殊情况,没有小于等于 key 的元素,也就是所有的都大于 key,那么在搜索过程中left
一直不变,right
一直在减小,结束时left = 0, right = -1
,要返回的就是-1;
int binarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while(left <= right) {
int mid = (right + left) / 2;
if(nums[mid] <= target)
left = mid + 1;
else if (nums[mid] > target)
right = mid - 1;
}
return right;
}
int binarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while(left <= right) {
int mid = (right + left) / 2;
if(nums[mid] < target)
left = mid + 1;
else if (nums[mid] >= target)
right = mid - 1;
}
return right;
}
int binarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while(left <= right) {
int mid = (right + left) / 2;
if(nums[mid] >= target)
right = mid - 1;
else if (nums[mid] < target)
left = mid + 1;
}
if(left < nums.length && nums[left] >= key) {
return left;
}
return -1;
}
int binarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while(left <= right) {
int mid = (right + left) / 2;
if(nums[mid] > target)
right = mid - 1;
else if (nums[mid] <= target)
left = mid + 1;
}
if(left < nums.length && nums[left] > key) {
return left;
}
return -1;
}