算法刷题套路和模板的GitHub仓库
二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。但是,二分查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。
二分查找算法是典型的「减治思想」的应用,我们使用二分查找将待查找的区间逐渐缩小,以达到「缩减问题规模」的目的。
比如查找升序数组nums里的目标值target(这里只讨论升序数组,降序数组是一样的道理):
int [] nums = { 0, 1, 2, 3, 4, 5, 6, 7 };
约定
我们把待查找区间的左边界下标设为left,右边界下标设为right,中间位置下标设为mid。
int left = 0;
int right = a.length - 1;
int mid = left + (right - left) / 2;
int mid = left + (right - left + 1) / 2;
int mid = left + (right - left) >> 1;
int mid = left + (right - left + 1) >> 1;
int mid = (right + left) >>> 1;
int mid = (right + left + 1) >>> 1;
打印数组的共通ArrayUtil
public class ArrayUtil {
public static void printArray(int[] arrays) {
StringBuilder sBuilder = new StringBuilder();
sBuilder.append("{ ");
for (int i : arrays) {
sBuilder.append(i + ", ");
}
sBuilder.delete(sBuilder.length() - 2, sBuilder.length());
sBuilder.append(" }");
System.out.println(sBuilder);
}
}
一、模板 1:while (left <= right)
1、思路:在循环体内部查找元素(解决简单问题时有用),即考虑下一轮目标元素应该在哪个区间
把待查找区间[left, right]分为 3 个部分:
-
mid位置(只有 1 个元素);
-
[left, mid - 1]里的所有元素;
-
[mid + 1, right]里的所有元素;
于是,二分查找就是不断地在区间[left, right]里根据中间元素nums[mid]与target的大小关系来不断缩小查找区间,最终找到target的下标:
-
nums[mid] == target时,返回mid;
-
nums[mid] > target时,由于数组升序,mid以及mid右边的所有元素都大于target,下一轮目标元素一定在区间[left, mid - 1]里,因此设置right = mid - 1;
-
nums[mid] < target时,由于数组升序,mid以及mid左边的所有元素都小于target,下一轮目标元素一定在区间[mid + 1, right]里,因此设置left = mid + 1。
2、图解



3、代码实现
public class BinarySearch {
public static void main(String[] args) {
int[] nums = { 0, 1, 2, 3, 4, 5, 6, 7 };
int target = 2;
System.out.print("数组 nums:");
ArrayUtil.printArray(nums);
System.out.println("目标值 target:" + target);
System.out.println("模板 1 下标:" + binarySearch1(nums, target));
}
public static int binarySearch1(int[] nums, int target) {
int len = nums.length;
if (len == 0) {
return -1;
}
int left = 0;
int right = len - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
return mid;
} else if (nums[mid] > target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return -1;
}
}
运行结果:
数组 nums:{ 0, 1, 2, 3, 4, 5, 6, 7 }
目标值 target:2
模板 1 下标:2
二、模板 2:while (left < right),推荐使用
1、思路:在循环体内部排除元素(解决复杂问题时非常有用),即考虑中间元素 nums[mid] 在什么情况下不是目标元素
把待查找区间[left, right]分为 2 个部分:
- 不存在目标元素(
if分支);
- 可能存在目标元素(
else分支,包含mid);
与模版 1 同样,二分查找就是不断地在区间[left, right]里根据中间元素nums[mid]与target的大小关系来不断缩小查找区间,最终找到target的下标:
①、中间位置下取整
-
nums[mid] < target时,mid以及mid左边元素都小于target,下一轮目标元素一定在区间[mid + 1, right]里,因此设置left = mid + 1。
②、中间位置上取整
-
nums[mid] > target时,mid以及mid右边元素都小于target,下一轮目标元素一定在区间[left, mid - 1]里,因此设置right = mid - 1;
Tips:先写if else分支,再决定是中间位置是上取整(target在左边)还是下取整(target在右边)。
特征:
-
while (left < right),这里使用严格小于 < 表示的临界条件是:当区间里的元素只有 2 个时,依然可以执行循环体。换句话说,退出循环的时候一定有 left == right 成立,这一点在定位元素下标的时候极其有用。
2、图解

3、代码实现
public class BinarySearch {
public static void main(String[] args) {
int[] nums = { 0, 1, 2, 3, 4, 5, 6, 7 };
int target = 2;
System.out.print("数组 nums:");
ArrayUtil.printArray(nums);
System.out.println("目标值 target:" + target);
System.out.println("模板 2(下取整)下标:" + binarySearch2_floor(nums, target));
System.out.println("模板 2(上取整)下标:" + binarySearch2_ceil(nums, target));
}
public static int binarySearch2_floor(int[] nums, int target) {
int len = nums.length;
if (len == 0) {
return -1;
}
int left = 0;
int right = len - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid;
}
}
return nums[left] == target ? left : -1;
}
public static int binarySearch2_ceil(int[] nums, int target) {
int len = nums.length;
if (len == 0) {
return -1;
}
int left = 0;
int right = len - 1;
while (left < right) {
int mid = left + (right - left + 1) / 2;
if (nums[mid] > target) {
right = mid - 1;
} else {
left = mid;
}
}
return nums[left] == target ? left : -1;
}
}
运行结果:
数组 nums:{ 0, 1, 2, 3, 4, 5, 6, 7 }
目标值 target:2
模板 2(下取整)下标:2
模板 2(上取整)下标:2
三、模板 3:while (left + 1 < right)
如果已经掌握了模板 2,就无需掌握这个模板,仅作了解。
1、与模版 2 的区别
这一版代码和模板 2 没有本质区别,一个显著的标志是:循环可以继续的条件是 while (left + 1 < right),这说明在退出循环的时候,一定有 left + 1 == right 成立,也就是退出循环以后,区间有 2 个元素,即 [left, right];
2、优缺点
- 优点:不用理解模板 2 在分支出现
left = mid 的时候中间位置上/下取整的行为;
- 缺点:
while (left + 1 < right) 写法相对于 while (left <= right) 和 while (left < right) 来说并不自然;由于退出循环以后,区间一定有两个元素,需要思考哪一个元素才是需要找的,即「后处理」一定要做,有些时候还会有先考虑 left 还是 right 的区别。
3、代码实现
public class BinarySearch {
public static void main(String[] args) {
int[] nums = { 0, 1, 2, 3, 4, 5, 6, 7 };
int target = 2;
System.out.print("数组 nums:");
ArrayUtil.printArray(nums);
System.out.println("目标值 target:" + target);
System.out.println("模板 3 下标:" + binarySearch3(nums, target));
}
public static int binarySearch3(int[] nums, int target) {
int len = nums.length;
if (len == 0) {
return -1;
}
int left = 0;
int right = len - 1;
while (left + 1 < right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
return mid;
} else if (nums[mid] < target) {
left = mid;
} else {
right = mid;
}
}
if (nums[left] == target) {
return left;
}
if (nums[right] == target) {
return right;
}
return -1;
}
}
运行结果:
数组 nums:{ 0, 1, 2, 3, 4, 5, 6, 7 }
目标值 target:2
模板 3 下标:2