在计算机科学中,二分搜索(英语:binary search),也称折半搜索(英语:half-interval search)[1]、对数搜索(英语:logarithmic search)[2],是一种在有序数组中查找某一特定元素的搜索算法。搜索过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组为空,则代表找不到。这种搜索算法每一次比较都使搜索范围缩小一半。
折半搜索每次把搜索区域减少一半,时间复杂度为 O(log n)。(n代表集合中元素的个数)
虽以递归形式定义,但是尾递归,可改写为循环。空间复杂度为O(1).
public static int rank(int key, int[] a) {
int low = 0;
int high = a.length - 1;
// int mid = (low + high) / 2;
// int mid = low + (high - low) / 2;//这行代码必须放在循环内部
while (low <= high) {
// int mid = low + (high - low) / 2;
int mid = low + ((high - low) >> 1); //不要忘记加括号,还有>> 1不是2
//最好最后检测等于这个情况
if (key == a[mid]) {
return mid;
} else if (key < a[mid]) {
high = mid - 1;
}else {
low = mid + 1;
}
}
return -1;
}
private static int binarySearch(int[] numbers, int start, int end, int key) {
while (start <= end) {
int mid = start + ((end - start) >> 1);
if (key < numbers[mid]) {
return binarySearch(numbers, start, mid - 1, key);
} else if (key > numbers[mid]) {
return binarySearch(numbers, mid + 1, end, key);
} else {
return mid;
}
}
return -1;
}
边界条件的界限
定义局部最小的概念。arr长度为1时,arr[0]是局部最小。arr的长度为N(N>1)时,如果arr[0]
public class Solution {
public int getLessIndex(int[] arr) {
if (arr == null || arr.length <= 0) {
return -1;
}
//arr长度为1时,arr[0]是局部最小
if (arr.length == 1) {
return 0;
}
//arr的长度为N(N>1)时,如果arr[0]
if (arr[0] < arr[1]) {
return 0;
}
//arr的长度为N(N>1)时,如果arr[N-1]
int N = arr.length;
if (arr[N-1] < arr[N-2]) {
return N-1;
}
int lo = 1, hi = N - 2;
while (lo <= hi) {
int mid = lo + ((hi - lo) >> 1);
if (arr[mid] > arr[mid-1]) {
hi = mid - 1;
} else if (arr[mid] > arr[mid+1]) {
lo = mid + 1;
} else {
return mid;
}
}
return lo;
}
}
对于一个有序数组arr,再给定一个整数num,请在arr中找到num这个数出现的最左边的位置。
给定一个数组arr及它的大小n,同时给定num。请返回所求位置。若该元素在数组中未出现,请返回-1。
思路:
由于数组有序,考虑用二分搜索法快速定位到数组中值为num位置处,找到后num后,因为数组有序递增,在num左边继续遍历,找到最左边的num位置
public int findPos(int[] arr, int n, int num) {
if(arr == null || arr.length == 1){
return -1;
}
int res = -1;
int left = 0;
int right = arr.length - 1;
int mid = 0;
while(left <= right){
mid = (left + right) / 2;
if(arr[mid] > num){
right = mid - 1;
}else if(arr[mid] < num){
left = mid + 1;
}else {//arr[mid] == num
res = mid;
//因为需要寻找最左边的,而数组也是有序的,因此,还需要往mid左边寻找最左的一个num值位置
right = mid -1;
}
}
return res;
}
对于一个有序循环数组arr,返回arr中的最小值。有序循环数组是指,有序数组左边任意长度的部分放到右边去,右边的部分拿到左边来。比如数组[1,2,3,3,4],是有序循环数组,[4,1,2,3,3]也是。
给定数组arr及它的大小n,请返回最小值。
测试样例:
[4,1,2,3,3],5
返回:1
import java.util.ArrayList;
//已知数组的旋转 求最小值
//特殊用例: 数组无旋转 2 . 大小为0 3. 元素有小于0
//方法一: 取巧的方式:遍历数组
//方法二: 用二分查找
public class Solution {
public int minNumberInRotateArray(int [] array) {
if (array == null || array.length <= 0) {
return 0;
}
int lo = 0, hi = array.length - 1;
while (lo <= hi) {
int mid = lo + ((hi - lo) >> 1);
if (array[mid] > array[hi]) {
lo = mid + 1;
} else if (array[mid] < array[hi]) {
hi = mid;
} else {
hi --;
}
}
return array[lo];
}
}
有一个有序数组arr,其中不含有重复元素,请找到满足arr[i]==i条件的最左的位置。如果所有位置上的数都不满足条件,返回-1。
给定有序数组arr及它的大小n,请返回所求值。
测试样例:
[-1,0,2,3],4
返回:2
public int findPos(int[] arr, int n, int num) {
if (arr == null || arr.length <= 0) {
return -1;
}
int lo = 0, hi = n - 1;
int res = -1;
while (lo <= hi) {
if (arr[lo] > lo || arr[hi] < hi) {
break;
}
int mid = lo + ((hi - lo) >> 1);
if (arr[mid] > mid) {
hi = mid - 1;
} else if (arr[mid] < mid) {
lo = mid + 1;
} else {
res = mid;
hi = mid - 1;
}
}
return res;
}
给定一棵完全二叉树的根节点root,返回这棵树的节点个数。如果完全二叉树的节点数为N,请实现时间复杂度低于O(N)的解法。
给定树的根结点root,请返回树的大小。
思路:
(1)首先一直遍历到根节点的左子树的最左边,得到树的高度high;
(2)再遍历根节点的右子树的最左边,得到右子树的高度rightHigh;
(3)如果high==rightHigh,表示根节点的左子树为满二叉树,高度为high,此时左子树可以直接根据满二叉树的性质求出节点数量,再对右子树递归遍历;
(4)如果high!=rightHigh,表示根节点的右子树为满二叉树,高度为high - 1,此时右子树可以直接根据满二叉树的性质求出节点数量,再对左子树递归遍历;
public int treeCount(TreeNode root) {
if (root == null) {
return 0;
}
int height = 0;
TreeNode node = root;
while (node != null) {
height++;
node = node.left;
}
int rightHeight = 0;
node = root;
while (node != null) {
rightHeight++;
node = node.right;
}
if (height == rightHeight) {
return (int) (Math.pow(2, height - 1) + treeCount(root.right));
} else {
return (int) (Math.pow(2, rightHeight - 1) + treeCount(root.left));
}
}
关于矩阵快速幂的应用
请见 魔力手环–2017网易编程应用题
public int pow(int a, int b) {
int res = 1;
while (b > 0) {
if ((b & 1) == 1) {
res *= a;
}
a *= a;
b >>= 1;
}
return res;
}