二分查找无序数组arr中的局部最小值的位置
提示:二分法系列文章
最基础的二分法:
二分法查找有序数组中的k所在的位置
二分法查找有序数组arr中,大于等于k的最左侧、最右侧的位置
题目
无数数组arr,一定保证任意位置i和i+1不相等
若[0]<[1],0位置算是局部最小
若[N-1]<[N-2],N-1位置算是局部最小
【注意,在今后的文章中,arr[i]=[i],我们用一个中括号简写即可,最开始你不习惯,但是你要习惯,慢慢就熟悉了】
其他的,所[i-1]>[i]且[i]<[i+1],则i位置是局部最小值。
请你找到病返回1个arr中的最小值。
一、审题
示例:arr=0 1 2 1 2 3 4 1 2;
显然,0位置就是局部最小值
212中间1那个位置也是局部最小值
只需要返回其中1个即可。
二、解题
这个题目,如果o(n)遍历,病按照条件判断也是可以的,但是就像前面找k一样,你是要费o(n)的时间的,n太大的话就失效了。
显然,查找这种事,就要瞬间想到二分查找,因为,二分真的真的很牛,能将算法复杂度降到o(log(n)),为啥不敏感地想到呢?
例如arr=3 1 2 1 2 3 4 1 2
(1)首先看0 1位置,显然3>1,3不会是局部最小
(2)再看N-2 N-1位置,显然,1<2,2不会是局部最小
(3)在L=1,R=N-2中二分查找
mid=0+8/2=4,mid处的2并不满足[i-1]>[i]且[i]<[i+1],故mid不是局部最小
——此时,随意判断,因为我们刚刚(1)看到了有一个[0]>[1],故如果此时[mid-1]<[mid]的话,令R=mid-1,这样子的话L–之间必然有一个是局部最小,大不了就是1位置。
——同理,刚刚我们看到了(2)[N-2]<[N-1],故此时再有[mid]<[mid+1]的话,令L=mid+1,这样子的话,L–R之间也必然有一个是局部最小,大不了就是N-2位置。
(4)一旦找到一个位置是局部最小,break即可,不找了。返回mid;
你仔细品,拿下面那个数组瞅瞅是不是这个道理!
手撕代码很简单:
//复习手撕代码:优化方法:二分法
public static int findLocalSmallest2Review(int[] arr){
if (arr == null || arr.length < 2) return -1;
int N = arr.length;
//看边界
if (arr[0] < arr[1]) return 0;
if (arr[N - 1] < arr[N - 2]) return N - 1;
int L = 1;//0和N-1不是不管了
int R = N - 2;
int index = -1;//结果,找不到就是-1
while (L <= R){
int mid = L + ((R - L) >> 1);
if (arr[mid - 1] < arr[mid]){
//必然mid不是局部最小值,条件都不满足,那左边也必然有一个最小值
R = mid - 1;
}else if (arr[mid] > arr[mid + 1]){
//也不然mid不是局部最小,右边必然,有一个是局部最小值
L = mid + 1;
}else {
//上面俩条件都不满足,必然是arr[mid - 1] > arr[mid]且arr[mid] < arr[mid + 1]
//这可不就是局部最小的定义吗
index = mid;
break;//找到了出去返回结果
}
}
return index;
}
自然有一个暴力解,但是,运气好的话,很快找到,如果运气不好,这个局部最小在R边上呢,N又很大,那就不行了
因此还是要二分法好。
//暴力解法,用于验证可靠性
public static int findLocalSmallest1(int[] arr){
//3种情况,第一种:0位置比1位置小,0位置是局部最小
if(arr[0] < arr[1]) return 0;
//第二种,N-1位置比N-2位置小,N-1位置是局部最小
if(arr[arr.length - 1] < arr[arr.length -2]) return arr.length - 1;
//从左往右遍历
//第三种,i-1位置比i位置大,i+1位置比i位置大,i位置是局部最小
int index = -1;
for (int i = 1; i < arr.length-2; i++) {
if(arr[i-1] > arr[i] && arr[i] < arr[i+1]){
index = i;
break;//找到就退出
}
}
return index;
}
测试代码:
//寻找一个局部最小的位置
//3种情况,第一种:0位置比1位置小,0位置是局部最小
//第二种,N-1位置比N-2位置小,N-1位置是局部最小
//第三种,i-1位置比i位置大,i+1位置比i位置大,i位置是局部最小
public class findLocalSmallest {
public static void main(String[] args) {
int[] arr = {2,1,3,1,2,1,5};//N=7
int pos = findLocalSmallest1(arr);
System.out.println(pos);
int pos1 = findLocalSmallest2(arr);
System.out.println(pos1);
int pos3 = findLocalSmallest2Review(arr);
System.out.println(pos3);
//上面俩方法都是对的,只要返回其一即可,不一定,但是二分法的计算效率很高,所以很棒。
}
总结
提示:重要经验:
1)理解清楚局部最小值的定义,则可以很快利用二分查找无序数组arr中的局部最小值的位置
2)二分查找的系列文章,这里暂时有3篇,前面开头那有2篇,一定好好理解,今后手撕不是问题,非常容易的。