局部最小问题(使用二分法)
/**
* 局部最小问题
* 已知一个数组arr,该数组无序,且相邻两个数不相等。
* 求:得出一个局部最小的值的索引。(返回一个即可)
* <p>
* 现约定:
* 如果第一个数比第二个数小,则第一个数称为局部最小。
* 如果最后一个数比倒数第二个数小,则最后一个数称为局部最小。
* 对于数组中间的数,如果它小于左边相邻的数,且小于右边相邻的数,则称这个数为局部最小。
* <p>
* 引申:使用二分法的前提,必须是数组有序吗?
* 答:不是必须。局部最小问题,就是反例。
*/
public class Code04_BSAwesome {
public static int oneMinIndex(int[] arr) {
int N = arr.length;
if (arr == null || N == 0) {
return -1;
}
if (N == 1) {
return 0;
}
//下边这两个if,描述的是数组两端的数,比较的情况。
if (arr[0] < arr[1]) {
return 0;
}
if (arr[N - 2] > arr[N - 1]) {
return N - 1;
}
int L = 0;
int R = N - 1;
//L <= R - 2,和L < R - 1,这两个条件等价,表述的都是,当前循环中,数组的长度,大于等于3。
//方法中使用R=mid-1和L=mid+1的前提是:mid-1和mid+1,都大于等于L,且都小于等于R。
//如果while中的条件写成(L < R),并且测试样本很大时,则一定会出现数组下标越界的情况。
//且越界的位置是,标记行①中的arr[mid - 1]。因为此时mid = 0,所以越界的index为-1.
//举例1:[3, 2, 3, 2, 3]。首先mid来到索引2位置,arr[mid]即3,不小于arr[1](即2),也不小于arr[3](即2)。
//所以L=mid-1,L=2-1,L=1。来到下一个循环,此时L=0,则mid=(0+1)/2,则mid=0。
//此时来到标记①这一行,因为mid=0,所以arr[mid-1]越界,越界的索引为-1。
//举例2:[89, 5, 42, 12, 89, 95],报错的情况同上。
while (L <= R - 2) {
// while (L < R - 1) {
int mid = L + ((R - L) >> 1);
//如果一个数既比左边相邻的数小,又比右边相邻的数小。则这个数称为局部最小。
if (arr[mid] < arr[mid - 1] && arr[mid] < arr[mid + 1]) {//标记行①
return mid;
} else {//否则
if (arr[mid] > arr[mid - 1]) {//这个数大于左边相邻的数。
R = mid - 1;//即,砍掉mid(含)的右边。看左边,去左边去找,局部最小的值的索引。
} else {//else的情况就是(arr[mid] < arr[mid] - 1)。因为相邻两个数不相等。
L = mid + 1;//即,砍掉mid(含)的左边。看右边,去右边去找,局部最小的值的索引。
}
}
}
//如果此时来到while循环外边,那么就只剩2个数了,比较一下,谁小谁就是局部最小。
return arr[L] < arr[R] ? L : R;
}
//随机生成一个数组,相邻数不相等。
public static int[] randomArr(int maxLen, int maxValue) {
int len = (int) (Math.random() * maxLen);
int[] arr = new int[len];
if (len > 0) {
//如果len>0,先随机生成第一个数。
arr[0] = (int) (Math.random() * maxValue);
for (int i = 1; i < len; i++) {
do {
arr[i] = (int) (Math.random() * maxValue);
} while (arr[i] == arr[i - 1]);//如果当前随机生成的数,和前一个数相等,就重新生成随机数。
}
}
return arr;
}
//用于测试,给一个数组arr,和局部最小值的索引minIndex。
//返回minIndex是否是arr中的局部最小值的索引。
/**
* @param arr 一个数组,相邻数不相等
* @param minIndex 局部最小值的索引
* @return minIndex是否真的是局部最小值的索引
*/
public static boolean check(int[] arr, int minIndex) {
if (arr.length == 0) {
return minIndex == -1;
}
int left = minIndex - 1;
int right = minIndex + 1;
//如果局部最小值左边的数的索引left,大于等于0,那么就让arr[left]和arr[minIndex]真去比一下;
//否则,返回true,即,局部最小值左边的数 > 局部最小值。
boolean leftBigger = left >= 0 ? arr[left] > arr[minIndex] : true;
// boolean leftBigger = left < 0 || arr[left] > arr[minIndex];//简化后
//如果局部最小值右边的数的索引right,小于数组arr的长度(最大也是arr.length - 1,即没有越界),那么就让arr[right]和arr[minIndex]真去比一下;
//否则,返回true,即,局部最小值右边的数 > 局部最小值。
boolean rightBigger = right < arr.length ? arr[right] > arr[minIndex] : true;
// boolean rightBigger = right >= arr.length || arr[right] > arr[minIndex];简化后
//如果局部最小值,它的左边大于它,并且右边也大于它。那么它,确实是局部最小。
return leftBigger && rightBigger;
}
public static void printArr(int[] arr) {
for (int value : arr) {
System.out.print(value + " ");
}
System.out.println();
}
public static void main(String[] args) {
int testTimes = 10000;
int maxLen = 20;
int maxValue = 100;
boolean flag = true;
System.out.println("测试开始!");
for (int i = 0; i < testTimes; i++) {
int[] arr = randomArr(maxLen, maxValue);
int minIndex = oneMinIndex(arr);
if (!check(arr, minIndex)) {
flag = false;
printArr(arr);
System.out.println("minIndex = " + minIndex);
break;
}
}
System.out.println("flag = " + flag);
System.out.println("测试结束!");
}
//测试数据
int[] arr1 = {71, 21, 91, 50, 38, 65, 87, /*diff*/90, 18, 65, 5, 57, 91, 16, 88};
//minIndex=8,arr[minIndex] = 18
int[] arr2 = {71, 21, 91, 50, 38, 65, 87, /*diff*/20, 18, 65, 5, 57, 91, 16, 88};
//minIndex=4,arr[minIndex] = 38
}
分类:
algorithm_novice
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」