二分法相对于其它算法来说是比较简单的,很容易理解,但是如果要完整的写出二分法的代码,还是有点挑战性的,可能会出现各种各样的问题,包括死循环、边界定义不清晰等,另外就是,可能传统的二分法我们都能理解,但是换了一下题目就不会用二分法的思想去解答了,以下附上个人比较喜欢的二分法模板:
public class 二分查找 {
/**
* 递归法
*/
public int search(int[] arr, int target, int left, int right) {
if (arr == null || arr.length == 0) {
return -1;
}
// 递归终止的条件
if (left > right) {
return -1;
}
int mid = (left + right) / 2;
if (target < arr[mid]) {
return search(arr, target, left, mid - 1);
} else if (target > arr[mid]) {
return search(arr, target, mid + 1, right);
} else {
return mid;
}
}
/**
* 非递归法/循环法
*/
public int search(int[] arr, int target) {
if (arr == null || arr.length == 0) {
return -1;
}
int left = 0;
int right = arr.length - 1;
int mid = 0;
while (left <= right) {
mid = (left + right) / 2;
if (target < arr[mid]) {
right = mid - 1;
} else if (target > arr[mid]) {
left = mid + 1;
} else {
return mid;
}
}
return -1;
}
}
注意:上面的代码中,对于递归解法,不要忘了写递归终止的条件,不然会陷入死循环,导致栈溢出;另外就是我们定义的左右两个边界值left和right,要区分开是左闭右闭还是左闭右开
leetcode上有非常多关于二分法的题目,一般来说,当题目提及“有序数组”等字样,我们就要想到能否用二分法来提高查询的效率,这里举一个典型的例子:
- 剑指 Offer 53 - I. 在排序数组中查找数字 I
题目描述:统计一个数字在排序数组中出现的次数。
示例 1:
输入: nums = [5,7,7,8,8,10], target = 8
输出: 2
示例 2:
输入: nums = [5,7,7,8,8,10], target = 6
输出: 0
// 统计一个数字在排序数组中出现的次数
public class 在排序数组中查找数字I {
/**
* 方法一:使用Map集合统计
* 思路简单,但是需要遍历整个数组,没有使用到数组已排好序这个特性,时间复杂度O(n)
* @param nums
* @param target
* @return
*/
public int search(int[] nums, int target) {
if (nums == null || nums.length == 0) {
return 0;
}
HashMap<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
if (map.containsKey(nums[i])) {
map.put(nums[i], map.get(nums[i]) + 1);
} else {
map.put(nums[i], 1);
}
}
if (map.containsKey(target)) {
return map.get(target);
} else {
return 0;
}
}
/**
* 方法二:使用非递归二分法
* @param nums
* @param target
* @return
*/
public int search1(int[] nums, int target) {
if (nums == null || nums.length == 0) {
return 0;
}
int left = 0;
int right = nums.length - 1;
int mid = 0;
int count = 0;
while (left <= right) {
mid = (left + right) / 2;
if (target < nums[mid]) {
right = mid - 1;
} else if (target > nums[mid]) {
left = mid + 1;
} else {
// 可能目标值不止一个,因此还需要继续往左和往右遍历,直到遍历到的值不等于目标值即可退出
count++;
int l = mid - 1;
int r = mid + 1;
while (l >= 0) {
if (nums[l] == target) {
count++;
l--;
} else {
break;
}
}
while (r <= nums.length - 1) {
if (nums[r] == target) {
count++;
r++;
} else {
break;
}
}
return count;//此处记得return,否则会死循环
}
}
return count;
}
/**
* 方法三:使用递归二分法
* @param nums
* @param target
* @return
*/
public int search(int[] nums, int target, int left, int right) {
if (nums == null || nums.length == 0) {
return 0;
}
if (left > right) {
return 0;
}
int mid = (left + right) / 2;
if (target < nums[mid]) {
return search(nums, target, left, mid - 1);
} else if (target > nums[mid]) {
return search(nums, target, mid + 1, right);
} else {
return search(nums, target, left, mid - 1) + search(nums, target, mid + 1, right) + 1;
}
}
}
上面的例题中,其实就是传统二分法的升级,传统的二分法是假设数组中没有重复的元素,那么当找到一个即可退出,本题是需要统计目标值出现的次数,也就是可能会有多个值,因此要注意当使用二分法查找到一个目标值之后,不能立即退出,还应该继续寻找