题目描述
给定无序数组arr,已知arr中任意两个相邻的数都不相等,只需要返回arr中任意一个局部最小值出现的位置即可。
- 定义局部最小的概念:
- arr长度为1时,arr[0]是局部最小;
- arr的长度为N(N>1)时,如果arr[0] < arr[1],那么arr[0]是局部最小;
- 如果arr[N-1]<arr[N-2],那么arr[N-1]是局部最小;
- 如果0<i<N-1,既有arr[i] < arr[i-1],又有arr[i] < arr[i + 1],那么arr[i]是局部最小。
题目链接(牛客网): www.nowcoder.com/questionTer…
N>1时,也就是下图中的三种情况:
输入描述:
第一行输入一个n代表下面需要输入n个数
第二行输入n个元素,任意两个相邻的数都不相等
输出描述:
返回arr中任意一个局部最小值出现的位置
例如,输入:
6//数组个数
6 2 3 1 5 6//数组内容
输出:
1//该例中的一个局部最小值是2,其下标为1,因此返回1.
思路解析
当我们读完题目之后,大多数人可能不会想到用二分查找来做,因为题目提供的数组是无序的,但事实上,我们真的可以用二分查找来解决。简单来说就跟求函数的极小值一样,可能只有一个也可能不只一个,由于题目中说任意两个相邻的数都不相等因此数组中不可能不存在局部最小值,我们可以根据元素的递增或者递减的趋势来判断是否存在局部最小。数组的长度大于1时,我们可以分为如下几个情况:
1、开头递增或末尾递减
若数组满足arr[0]<arr[1]或arr[N-2]>arr[N-1],那么如下图所示:
由图可知,若满足这两种情况之一,局部最小值只需进行一次判断即可找出,但是如果这两种情况都不满足呢?
2、开头递减且末尾递增
如图所示,我们可以观察出,该数组开头是呈递减的趋势,末尾是呈递增的趋势,我们可以得出一个结论,那就是:在arr[0]到arr[N-1]之间,一定存在局部最小值。因为在中间这n个元素中,一定存在一个拐点,改变了开头递减的情况,使其最后变成了递增,因此我们可以根据这一特点,用二分查找求他的局部最小值。
3、二分查找求最小值
我们取数组的中间元素arr[mid]:
- 如果
arr[mid-1]
、arr[mid+1]
,都比arr[mid]
大,则直接返回mid即可;- 如果
arr[mid]
大于arr[mid-1]
,则arr[mid-1]
到arr[mid]
的趋势递增
,跟arr[N-2]
到arr[N-1]
的趋势一样,说明在arr[0]
到arr[mid]
的范围内,一定存在局部最小;- 如果
arr[mid]
大于arr[mid+1]
,则arr[mid]
到arr[mid+1]
的趋势递减
,跟arr[0]
到arr[1]
的趋势一样,说明在arr[mid]
到arr[N-1]
的范围内,一定存在局部最小。
因此我们只需根据这三种条件来进行二分即可。
完整代码及解析
package 苟熊岭熊哒;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class LocalMinNum {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(br.readLine());// 输入数组的长度
String[] line = br.readLine().split(" ");// 切割字符串
int[] arr = new int[n];// 创建长度为n的数组
for (int i = 0; i < arr.length; i++) {
arr[i] = Integer.parseInt(line[i]);
}
// 以上代码为输入过程,刷题时可无视
int result = local_minimum(arr);// 查找局部最小值
System.out.println(result);// 输出结果
}
// 查找局部最小
public static int local_minimum(int[] arr) {
if ((arr.length == 1) || (arr[0] < arr[1])) {
// 根据题意,如果数组长度为1或者满足arr[0]<arr[1],则arr[0]是局部最小
return 0;
} else if (arr[arr.length - 2] > arr[arr.length - 1]) {
// 如果arr[N-2]>arr[N-1],那么arr[N-1]是局部最小
return arr.length - 1;
}
int left = 0;// 数组左边第一个元素的下标
int right = arr.length - 1;// 数组最后一个元素的下标
int mid = left + ((right - left) >> 1);// 中间值的下标
// 二分查找局部最小值
while (true) {// 因为任意两个相邻的数互不相等,故数组内一定存在局部最小值
if ((arr[mid - 1] > arr[mid]) && (arr[mid + 1] > arr[mid])) {
return mid;// 当arr[mid]比他两边的数都小,则直接返回mid
} else if (arr[mid - 1] < arr[mid]) {
right = mid;// 说明arr[mid]左边一定存在拐点
mid = left + ((right - left) >> 1);// 给中点重新赋值
continue;
} else if (arr[mid + 1] < arr[mid]) {
left = mid;// 说明arr[mid]右边一定存在拐点
mid = left + ((right - left) >> 1);// 给中点重新赋值
continue;
}
}
}
}
输出结果:
这道题的介绍到这里就结束了,如果你觉得本篇文章对你多少有些帮助,可以点个赞或者收藏一波支持一下哦,欢迎各位大佬批评指正,咱们下次再见!