注:此篇文章,基于前Google工程师王争编写的著作《数据结构与算法之美》,加以自身理解,编写而成。
前言
二分查找的变体问题有很多,我们选择其中4个比较常见的来讲解。如图所示:
需要说明的是,这里为了简化讲解,本文章内容都以数据从小到大排序为前提。对于从大到小排序的数据,解决思路是类似的。
变体问题1
查找第一个值等于给定值的元素的索引位置,若找不到,返回-1。
代码实现
private static int bsearch(int[] array, int value) {
int len = array.length;
if (len == 0) {
return -1;
}
int left = 0;
int right = len - 1;
while (left < right) {
int mid = left + ((right - left) >> 1);
if (array[mid] < value) {
left = mid + 1;
} else {
right = mid;
}
}
return array[left] == value ? left : -1;
}
解题思路
解题的思路关键在于,我们需要找到最后一个小于value的元素,然后再判断此元素的后面那个元素,是否等于value,若是,返回其索引值,否则返回-1。注意while循环条件是left<right而不是left <= right。否则当数组仅有一个元素的时候,会出现死循环。
变体问题2
查找最后一个值等于给定值的元素的索引位置,若找不到,返回-1:
代码实现
public static int bsearch(int[] a, int value) {
int low = 0;
int high = a.length - 1;
while (low <= high) {
int mid = low + ((high - low) >> 1);
if (a[mid] > value) {
high = mid - 1;
} else if (a[mid] < value) {
low = mid + 1;
} else {
if ((mid == a.length - 1) || (a[mid + 1] != value)) {
return mid;
}
else {
low = mid + 1;
}
}
}
return -1;
}
解题思路
解题思路和普通的二分写法类似,只不过中点元素mid等于给定值value的时候,需要判断mid的下一个元素是否下标越界或者不等于value,若满足,则返回mid值。否则,将mid + 1的值赋值给low,让查找比对的mid元素,整体右移。
变体问题3
查找第一个值大于或等于给定值的元素的索引位置,若找不到,返回-1:
代码实现
private static int bsearch(int[] array, int value) {
int len = array.length;
if (len == 0) {
return -1;
}
int left = 0;
int right = len - 1;
while (left < right) {
int mid = left + ((right - left) >> 1);
if (array[mid] < value) {
left = mid + 1;
} else {
right = mid;
}
}
return array[left] >= value ? left : -1;
}
解题思路
解题思路和变体问题1类似,只不过在返回值的时候判断条件变了,条件变为array[left] >= value。
变体问题4
查找最后一个值小于或等于给定值的元素的索引位置,若找不到,返回-1:
代码实现
public static int bsearch(int[] a, int value) {
int low = 0;
int high = a.length - 1;
while (low <= high) {
int mid = low + ((high - low) >> 1);
if (a[mid] > value) {
high = mid - 1;
} else {
if ((mid == a.length - 1) || (a[mid + 1] > value)) {
return mid;
}
else {
low = mid + 1;
}
}
}
return -1;
}
解题思路
解题思路和普通的二分写法类似,只不过中点元素mid小于等于给定值value的时候,需要判断mid的下一个元素是否下标越界或者大于value,若满足,则返回mid值。否则,将mid + 1的值赋值给low,让查找比对的mid元素,整体右移。