二分查找算法
一个简单的查找算法其实也有很多坑:
1、注意left和right的定义,一定是-1,不然会死循环
2、mid需要写成上述形式,这样数据量过大时也可以进行运算
3、注意break;
变式一
这时面试官觉得你的代码不错,可能就会问了,如果要查找的数存在多个怎么才能找到最左边的值
例:1,2,2,2,2,2,3 查找2
此时需要将else块的代码改写:
public static int FindValue1(int []arr,int val){
int pos=-1;
if(arr==null)return pos;
int left=0,right=arr.length-1;
while (left<=right){
int mid=(right-left+1)/2+left;
if(arr[mid]<val){
left=mid+1;
}else if(arr[mid]>val){
right=mid-1;
}else {
while (mid>left&&arr[mid-1]==val){
mid--;
}
pos=mid;
break;
}
}
return pos;
}
变式二
这时面试官可能觉得你不错,继续问了,如果我有很多的重复的,这样用while循环一个一个比较会不会导致效率太低,如何提高算法效率?
这时我们就要思考,既然一个个查找太慢,我们就一块一块来查找,二分查找本来就是将数据分区域,根据mid来划分,我们要找的第一个的特点是大于前一个数,所以就可以分为两块,大于的一块,小于的一块,
1、观察退出时的状态,从左边退出时正好left=mid+1;所以我们可以利用这个特点,return left;
2、这道题的第二个坑,不论有没有找到left都会赋值,这时候我们就要对比left的值是不是我们要找的值
public static int banrySerch(int[] arr, int temp) {
int result = -1;
if (arr == null) {
return result;
}
int left = 0;
int right = arr.length - 1;
while (left <= right) {
int mid = (right - left + 1) / 2 + left;
if (arr[mid] < temp) {
left = mid + 1;
} else if (arr[mid] > temp) {
right = mid - 1;
} else {
right = mid - 1;
}
}
if (arr[left] == temp) {
return left;
} else
return result;
}
变式三
面试官可能觉得写得不错,继续追问
一个有序的二维数组,每一行的第一个数字大于前一行的最后一个数字,每一行的数字有序,在其中查找值?
这时我们可能会想到,牛客网中的第一题,他的思路是从左下角向右上角查找,可是两道题的区别在于这个是每一行的第一个数字大于上一行的最后一个数字,牛客网的那道题只是每一行数字有序;
这时我们应该想到,将二维数组展开,变成一个有序的一维数组,此时可以用二分查找。
有了核心思想 代码怎么实现呢?
public static boolean Find(int [][]ar ,int target){
int m=ar.length;
if(m==0)return false;
int n=ar[0].length;
int left=0,right=m*n-1;
while (left<=right){
int mid=(right-left+1)/2+left;
if(ar[mid/n][mid%n]==target){
return true;
}else if(ar[mid/n][mid%n]<target){
left=mid+1;
}else
right=mid-1;
}
return false;
}
我们的二维数组是行优先存储
反向变式
贪吃的小明:
小明的父母要出差N天,走之前给小明留下了M块奶糖,小明决定每天吃的奶糖数量不少于前一天吃的一半,但是他又不想在父母回来之前的某一天没有奶糖吃,请问他第一天最多能吃多少块奶糖?
分苹果问题
根据同学的成绩给同学发苹果,要求:每个同学至少发一个;相邻的同学分数高的一定比分数低的苹果多,问你老师至少需要准备多少苹果?例如[1,0,1] --> 第一个人分2个,第二个人分1个,第三个人分2个,一共5个。例如[3,5,5] --> 第一个人分1个,第二个人分2个,第三个人分1个,一共4个
这道题乍一看还可以,但是当进入思维定式,只进行一个方向的比较,遍历时,问题就会非常复杂,此时,用双指针思想,两次遍历,比如:创建两个数组,值都初始化为1,left从1下标开始,比较i-1前一个下标的值;right从lenght-2位置开始,比较i+1的下标的值,如果大则加上比较的那个下标的值,然后比较left和right的值谁大取谁;
public static int getApple(int []ar){
if(ar==null)return -1;
if(ar.length==1)return 1;
int sum=0;
int []left=new int[ar.length];
int []right=new int[ar.length];
for(int i=0;i<ar.length;++i){
left[i]=right[i]=1;
}
int i=1;
while (i<ar.length-1){
left[i]=ar[i]>ar[i-1]?left[i-1]+1:left[i];
++i;
}
int j=ar.length-2;
while (j>=0){
right[j]=ar[j]>ar[j+1]?right[j+1]+1:right[i];
--j;
}
// for(int i=1;i<ar.length;++i){
// if(ar[i]>ar[i-1]) left[i]=left[i-1]+1;
// }
// for(int i=ar.length-2;i>=0;--i){
// if(ar[i]>ar[i+1])right[i]=right[i+1]+1;
// }
for(int k=0;k<ar.length;k++){
sum+=left[k]>right[k]?left[k]:right[k];
}
return sum;
}
查询重复的整型数
给定一个包含n+1个整数的数组ar,其数字都在1到n之间(包括1和n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。
例如:input :ar[]={1,3,4,2,5,2}
output:2
规则如下:
不能更改原数组(假设数组是只读的)
只能使用额外的O(1)的空间
时间复杂度小于O(n*n)
数组中只有一个重复的数字,但它可能不止重复出现一次
查询第n小的整型数值
有二个数组ar,br,大小都是n,并且都是升序排序,在ar或br查找第n小的数值。
例如 ar[]={1,3,5,7,9}//n=5
br[]={2,4,6,8,10}//n=5
output=5
规则如下:
不能更改原数组(假设数组是只读的)
只能使用额外的O(1)的空间
时间复杂度小于O(n)
数组中有重复的数值
持续更新~