二分搜索常见的应用场景
1.在有序序列中查找一个数
2.并不一定非要在有序序列中才能得到应用
二分搜索常见的考察点
1.对于边界条件的考察以及代码实现的能力
2.在有序循环数组中进行二分搜索
二分搜索常见题目的变化
1.给定处理或查找的对象不同
2.判断条件不同
3.要求返回的内容不同
二分搜索的重要提醒
mid= (left+right)/2 -->可能溢出
更安全的写法:mid=left+(right-left)/2
例1:
给定一个无序数组arr,已知任意两个相邻的两个元素,值都不重复。请返回任意一个局部最小的位置。
所谓局部最小的位置是指,如果arr[0]<arr[1],那么位置0就是一个局部最小的位置。如果arr[N-1](也就是arr最右的数)小于arr[N-2]那么位置N-1也是局部最小的位置。如果位置i既不是最右也不是最左位置。那么只要满足arr[i]同时小于它左右两侧的值即(arr[i-1]和arr[i+1]),那么位置i也是一个局部最小的位置。
解:
本题依然可以用二分搜索来实现时间复杂度为O(logN)
1.arr为空或长度为0,返回-1,表示局部最小位置不存在。
2.如果arr长度为1,返回0,因为此时0是局部最小位置。
3.如果arr长度大于1
代码:
public int getLessIndex(int[] arr) {
if (arr == null || arr.length == 0) {
return -1;
}
if (arr.length == 1 || arr[0] < arr[1]) {
return 0;
}
if (arr[arr.length - 1] < arr[arr.length - 2]) {
return arr.length - 1;
}
int left = 1;
int right = arr.length - 2;
int mid = 0;
while (left < right) {
mid = (left + right) / 2;
if (arr[mid] > arr[mid - 1]) {
right = mid - 1;
} else if (arr[mid] > arr[mid + 1]) {
left = mid + 1;
} else {
return mid;
}
}
return left;
}
例2:
给定一个有序数组arr,在给定一个整数num,请在arr中找到num这个数出现的最左边的位置。
假设:
1 2 3 3 3 3 4 4 4 4 4 4 4 4 4
res=-1(最后一个num的位置) num=3
解:
首先找到中间的数,右边都是大于3的数、更新res,直接在左部分继续进行搜索,找到3之后,继续在左部分搜索
代码:
public int findPos(int[] arr, int n, int num) {
if (arr == null || n == 0) {
return -1;
}
int left = 0;
int right = arr.length - 1;
int mid = 0;
int res = -1;
while (left <= right) {
mid = (left + right) / 2;
if (arr[mid] < num) {
left = mid + 1;
} else if (arr[mid] > num) {
right = mid - 1;
} else {
res = mid;
right = mid - 1;
}
}
return res;
}
例3:
给定一个有序循环数组arr,返回arr中的最小值。有序循环数组是指,有序数组左边任意长度的部分放到右边去,右边的部分拿到左边来。比如数组[1,2,3,3,4],其有序循环数组是[4,1,2,3,3]。
解:
下标:L=0,R=N-1,M=L+R/2
如果arr[L]<arr[R] ?这说明整体是有序的,直接返回arr[L]即可
如果arr[L]>=arr[R],说明包含循环,此时看arr[L]>arr[M],说明最小值只有可能出现在L到M范围上,因为只有arr[M]只有是循环过的数时才有可能发生arr[L]>arr[M],当arr[M]>arr[R]时M到R是循环部分,所以从M到R开始二分查找,当满足arr[L]>=arr[R] ,arr[L]<=arr[M] ,arr[M]<=arr[R],此时只能用遍历的方式在arr[R]到arr[L]查找最小值。
代码:
public int getMin(int[] arr, int n) {
if (arr == null || arr.length == 0) {
return -1;
}
int low = 0;
int high = arr.length - 1;
int mid = 0;
while (low < high) {
if (low == high - 1) {
break;
}
if (arr[low] < arr[high]) {
return arr[low];
}
mid = (low + high) / 2;
if (arr[low] > arr[mid]) {
high = mid;
continue;
}
if (arr[mid] > arr[high]) {
low = mid;
continue;
}
while (low < mid) {
if (arr[low] == arr[mid]) {
low++;
} else if (arr[low] < arr[mid]) {
return arr[low];
} else {
high = mid;
break;
}
}
}
return Math.min(arr[low], arr[high]);
}
例4:
给定一个有序数组arr,其中不含有重复元素,请找到满足arr[i]==i条件的最左的位置。如果所有位置上的数都不满足条件,返回-1。
解:
arr[0]>N-1,返回-1;
arr[N-1]<0,返回-1;
arr[M]>M,位置增量是1,所以只需在0到M上搜索;
arr[M]<M,只需在M+1到N的范围继续二分;
arr[M] ==M,res=M记录,然后继续在0到M-1二分;
代码:
public int findPos(int arr[], int n) {
if (arr == null || n == 0) {
return -1;
}
int left = 0;
int right = n - 1;
int res = -1;
while (left <= right) {
if (arr[left] > left || arr[right] < right) {
break;
}
int mid = (left + right) / 2;
if (arr[mid] < mid) {
left = mid + 1;
} else if (arr[mid] > mid) {
right = mid - 1;
} else {
res = mid;
right = mid - 1;
}
}
return res;
}
例5:
给定一个完全二叉树的头节点head,返回这棵树的节点个数,如果完全二叉树的节点为N,请实现时间复杂度低于O(N)的解法。
解:
首先找到二叉树最左的节点,目的是统计完全二叉树的高度,最左的几点肯定是在完全二叉树最后一层,然后找到二叉树头节点的右子树的最左节点,如果这个节点能够到达最底层,说明头节点的左子树是一个满的二叉树,可以通过公式算出左子树的节点数,剩下的节点数可以递归的算出右子树的节点个数;如果头节点的右子树的最左节点不能到达最后一层,说明右子树是满的二叉树(高度-1),通过公式直接求得右子树的节点数。然后递归额算出左子树的节点数
遍历的方式,时间复杂度为O(N)
最优解的过程,时间复杂度为O(logN^2)
代码:
public int count(TreeNode head) {
if (head == null) {
return 0;
}
return bs(head, 1, mostleftLevel(head, 1));
}
private int bs(TreeNode node, int l, int h) {
if (l == h) {
return 1;
}
if (mostleftLevel(node.right, l + 1) == h) {
return (1 << (h - 1)) + bs(node.right, l + 1, h);
} else {
return (1 << (h - 1)) + bs(node.left, l + 1, h);
}
}
private int mostleftLevel(TreeNode node, int level) {
while (node != null) {
level++;
node = node.left;
}
return level - 1;
}
例6:
如果更快的求一个整数K的N次方。如果两个整数相乘并得到结果的时间复杂度为O(1),得到整数K的N次方的过程请实现时间复杂度O(logN)的方法。
解:
10^75=10 ^1001011(75的二进制表达)
=10 ^64* 10 ^8* 10 ^2 *10 ^1(二进制相应的位置上是1)
=10 ^1000000 * 10 ^1000 *10 ^10 *10 ^1(二进制表达)
思想原型来自二分搜索
代码:
public int getPower(int a, int n) {
BigInteger res = BigInteger.valueOf(1);
BigInteger tmp = BigInteger.valueOf(a);
for (; n != 0; n >>= 1) {
if ((n & 1) != 0) {
res = res.multiply(tmp);
}
tmp = tmp.multiply(tmp);
res = res.mod(BigInteger.valueOf(1000000007));
tmp = tmp.mod(BigInteger.valueOf(1000000007));
}
return res.mod(BigInteger.valueOf(1000000007)).intValue();
}