常见二分查找
思路就是在利用原本序列有序的性质,将中间元素和寻找的元素进行比较,从而筛掉一半不用比较的数。 由于有序性质一直存在,即使筛到剩下两个,也可以通过比较其中一个元素得知与另一个元素的关系。
//递归写法
public int sortBin(int[] ints,int target,int start,int end){
//常规二分法思路简单,两个指针l,r,通过target和mid比较更小mid到l或者r //二分法可以递归,也可以while true下靠近两侧指
if(end == start){
return start;
}
int mid = start +((end-start)>>1);
int midValue = ints[mid];
if(target < midValue){
return sortBin(ints, target, start, mid);
}else if(target > midValue){
return sortBin(ints, target, mid + 1, end);
}else {
return mid;
}
}
//双指针写法
public int sortBinLR(int[] ints,int target){
int l = 0;
int r = ints.length-1;
while (l<r){
int mid = l + ((r - l) >> 1);
if(target < mid){
r = mid-1;
}else if(target > mid){
l = mid+1;
}else {
return mid;
}
}
return l;
}
例1: 给定一个有序数组和其中一个数target,获取target出现的最左边的index
例如:{1,1,1,2,3,4,4,4,4,5,6,7,7,7,7},找出最左边的4
一般想法自然是O(N)一波遍历,但用二分法任能做到O(logN)
这个题为什么可以用二分法?二分法关键的一步是:通过对中间元素mid和目标元素target比较后能否确保一半元素没用。按照上面的例子,第一波mid = 4,可能是最左边4的也可能最左边的4在mid左边,但可以确保右边元素淘汰。 代码:
public void sortBin2(int[] ints,int target,int start,int end){
if(end - start == 1){
System.out.println(start);
}
int mid = (start + end) / 2;
int midValue = ints[mid];
if(target <= midValue){
sortBin2(ints,target,start,mid);
} else {
sortBin2(ints,target,mid+1,end);
}
}
例2:一个无序数组,寻找一个局部最小值,要求是时间复杂度优于O(n)
局部最小值:如果某个数左右有数的话,需要左右两边都比此数大,对于两边的数只需要小于其一头 例如: {32,31,3,5,3,2,31,5,4,7,6,4,2,24,47,3}中索引为2的3
无序怎么用二分法?这个题目的思路并非是用二分法筛掉不符合特征的数,而是筛掉不符合特征的趋势。
题目中想要局部最小值,其实就是一个凹的形状;
首先可以把凹形在两头的可能性验证一下。
除此之外,两头相邻的数一定比两头的数更小,具体是有哪些可能?
题目要求我们找到一个局部最小值,用二分法该怎么考虑? 如果我们获取到mid,能不能抛弃掉一半的数?
当然可以———假如一个函数一开始一阶导小于0,最后大于0,那么一定至少存在一个一阶导等于0的点;并且如果某个点一阶导小于0(下降),一定可以判断右侧存在一个拐点;如果某个点一阶导大于0(上升),一定可以判断左侧存在一个拐点——于是可以筛掉一半的数
public void sortBin3(int[] ints,int start,int end){
if(end - start == 2){
System.out.println((end+start)/2);
return;
}
int mid = (start + end)/2;
if(ints[mid-1]<ints[mid]){
sortBin3(ints,start,mid);
}else{
sortBin3(ints, mid,end);
}
}
例3:给你一个非负整数 x ,计算并返回 x 的 算术平方根 。
由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。 LeetCode 69
同样的思路:由于暴力遍历的过程是有序的,可以用二分法将O(N)优化为O(logN)
此题有几点值得注意:
- 为了防止在运算中超过数据类型的规定范围,一般会将取中间值写为:mid = start + ((end - start)>> 1),会将乘法运算比大小变成除法运算比大小:x*x<y --> x<y/x
- LeetCode官方提供了一种题解:通过算数变化简化了运算逻辑:
优雅,太优雅了
public int sqrtD(int x, int start, int end) { //end = x 包括最后一位
if (end - start <= 1) {
return end * end == x ? end : start;
}
int mid = start + ((end - start) >> 1);
int ans;
if (x / mid < mid) {
ans = sqrtD(x, start, mid);
} else if (x / mid > mid) {
ans = sqrtD(x, mid, end);
} else {
ans = mid;
}
return ans;
}
例4:LeetCode 81
已知存在一个按非降序排列的整数数组 nums ,数组中的值不必互不相同。
在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转 ,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,4,4,5,6,6,7] 在下标 5 处经旋转后可能变为 [4,5,6,6,7,0,1,2,4,4] 。
给你 旋转后 的数组 nums 和一个整数 target ,请你编写一个函数来判断给定的目标值是否存在于数组中。如果 nums 中存在这个目标值 target ,则返回 true ,否则返回 false 。
你必须尽可能减少整个操作步骤。
此题和LeetCode 153,154想法类似
思路:首先研究这个数组是什么样子:先增,骤减,再增;在此图像中怎么使用二分法查找?
画图很好疏通思路:
情况1:
情况2:
思路很明显了,在两种情况下,要么递归,要么用朴素二分法
但这个题目有一个问题:假如说是这两个数组 {1,1,1,1,1,mid(1),1,1,13,1,1}
{1,1,13,1,1,mid(1),1,1,1,1,1}
实际上是没法轻松判别mid左右哪个是有序的序列,此时这个方法就只能退化成群体遍历,O(N)
代码:
public boolean search(int[] nums, int target) {
return searchD(nums, target, 0, nums.length - 1);
}
public boolean searchD(int[] nums, int target, int start, int end) {
if (end - start <= 1) {
return nums[start] == target || nums[end] == target;
}
int mid = start + ((end - start) >> 1);
if (nums[mid] > nums[nums.length - 1]) { //mid在前半截
if (target < nums[mid]) {
return searchBin(nums, target, start, mid);
} else if (target == nums[mid]) {
return true;
} else {
return searchD(nums, target, mid, end);
}
} else {
if (target > nums[mid]) {
return searchBin(nums, target, mid, end);
} else if (target == nums[mid]) {
return true;
} else {
return searchD(nums, target, start, mid);
}
}
}
public boolean searchBin(int[] ints, int target, int start, int end) { //包括end
if (start == end) {
return target == ints[start];
}
int mid = start + ((end - start) >> 1);
if (target < ints[mid]) {
return searchBin(ints, target, start, mid);
} else if (target > ints[mid]) {
return searchBin(ints, target, mid + 1, end);
} else {
return true;
}
}
待续...