经常见到的类型是在一个有序数组上,开展二分搜索但有序真的是所有问题求解时使用二分的必要条件吗?
不!!!只要能正确构建左右两侧的淘汰逻辑,你就可以二分。
例题:
1. 在一个有序数组中,找某个数是否存在
package com.zhang.chapter01;
import java.util.Arrays;
import static com.zhang.chapter01.util.RandomArrayUtil.generateRandomArray;
import static com.zhang.chapter01.util.RandomArrayUtil.generateRandomSortedArray;
/**
* @author zhang
* @date 2023/11/21 14:01
* @description 二分查找 查找数组中是否存在一个数
*/
public class Code04_BSExist {
/**
* 二分查找
*
* @param sortedArr 有序数组
* @param target 目标
* @return true 存在 false 不存在
*/
public static boolean binarySearch(int[] sortedArr, int target) {
if (sortedArr == null || sortedArr.length == 0) {
return false;
}
int left = 0;
int right = sortedArr.length - 1;
while (left <= right) {
// 可能出现int溢出
// int mid = (left + right) / 2;
int mid = left + ((right - left) >> 1); // 右移运算符来代替除法运算符
if (sortedArr[mid] == target) {
return true;
} else if (sortedArr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return false;
}
public static void main(String[] args) {
int maxSize = 100;
int maxValue = 100;
int testTime = 50000;
for (int i = 0; i < testTime; i++) {
int[] sortedArray = generateRandomSortedArray(maxSize, maxValue);
int value = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
boolean result = binarySearch(sortedArray, value);
int index = Arrays.binarySearch(sortedArray, value);
if (result != (index >= 0)) {
System.out.println("binarySearch error!");
}
}
System.out.println("binarySearch is ok!");
}
}
2. 在一个有序数组中,找>=某个数最左侧的位置(找到第一次出现的位置)
package com.zhang.chapter01;
import java.util.Arrays;
import static com.zhang.chapter01.util.RandomArrayUtil.generateRandomSortedArray;
/**
* @author zhang
* @date 2023/11/21 14:23
* @description 二分查找法: 找满足 >=target 的最左位置
*/
public class Code05_BSNearLeft {
/**
* 二分查找法: 找满足 >=target 的最左位置
* @param arr 有序数组
* @param target 目标值
* @return 最左的元素的下标
*/
public static int nearestIndex(int[] arr, int target) {
if (arr == null || arr.length == 0) {
return -1;
}
int left = 0;
int right = arr.length - 1;
// 记录最左的对号
int index = -1;
while (left <= right) {
int mid = left + ((right - left) >> 1);
if (arr[mid] >= target) {
index = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
return index;
}
public static int test(int[] arr, int value) {
for (int i = 0; i < arr.length; i++) {
if (arr[i] >= value) {
return i;
}
}
return -1;
}
public static void main(String[] args) {
int maxSize = 100;
int maxValue = 100;
int testTime = 50000;
for (int i = 0; i < testTime; i++) {
int[] sortedArray = generateRandomSortedArray(maxSize, maxValue);
int value = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
int nearestIndex = nearestIndex(sortedArray, value);
int index = test(sortedArray, value);
if (nearestIndex != index) {
System.out.println("binarySearchNearLeft error!");
}
}
System.out.println("binarySearchNearLeft is ok!");
}
}
3. 在一个有序数组中,找<=某个数最右侧的位置
package com.zhang.chapter01;
import java.util.Arrays;
import static com.zhang.chapter01.util.RandomArrayUtil.generateRandomSortedArray;
/**
* @author zhang
* @date 2023/11/21 14:28
* @description 二分查找: 找满足 <=target 的最右位置
*/
public class Code05_BSNearRight {
/**
* 二分查找: 找满足 <=target 的最右位置
* @param arr 有序数组
* @param target 目标值
* @return 最右的元素的下标
*/
public static int nearestIndex(int[] arr, int target) {
if (arr == null || arr.length == 0) {
return -1;
}
int left = 0;
int right = arr.length - 1;
int index = -1;
while (left <= right) {
int mid = left + ((right - left) >> 1);
if (arr[mid] <= target) {
index = mid;
left = mid + 1;
} else {
right = mid - 1;
}
}
return index;
}
public static int test(int[] arr, int value) {
for (int i = arr.length - 1; i >= 0; i--) {
if (arr[i] <= value) {
return i;
}
}
return -1;
}
public static void main(String[] args) {
int maxSize = 10;
int maxValue = 100;
int testTime = 50000;
for (int i = 0; i < testTime; i++) {
int[] sortedArray = generateRandomSortedArray(maxSize, maxValue);
int value = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
int nearestIndex = nearestIndex(sortedArray, value);
int index = test(sortedArray, value);
if (nearestIndex != index) {
System.out.println("sortedArray = " + Arrays.toString(sortedArray));
System.out.println("value = " + value);
System.out.println("binarySearchNearRight error! nearestIndex = " + nearestIndex + " index = " + index);
return;
}
}
System.out.println("binarySearchNearRight is ok!");
}
}
重点:局部最小值问题 (无需有序数组也可以二分)
package com.zhang.chapter01;
import java.util.Arrays;
import static com.zhang.chapter01.util.RandomArrayUtil.generateRandomArrayWithNoAdjacent;
/**
* @author zhang
* @date 2023/11/21 14:41
* @description 无序数组的局部最小值问题
* 局部最小值问题定义:
* 数组中(无论有序还是无序 ) 任意相邻两个数不相等, 数组下标 i 的元素 小于 i - 1 下标的元素 而且 小于 i + 1 下标的元素, 就称为局部最小值.
* 特例:
* 1. 数组中只有一个元素, 这个元素就是 局部最小值.
* 2. 数组0下标的元素 只要小于右边的元素, 就称为局部最小值.
* 3. 数组最后一个元素 只要小于左边的元素, 就称为局部最小值.
*/
public class Code06_BSAwesome {
/**
* 局部最小值问题
*
* @param arr 数组
* @return 局部最小值下标
*/
public static int getLessIndex(int[] arr) {
if (arr == null || arr.length == 0) {
return -1;
}
if (arr.length == 1 || arr[0] < arr[1]) {
return 0;
} else if (arr[arr.length - 1] < arr[arr.length - 2]) {
return arr.length - 1;
}
int left = 1;
int right = arr.length - 2;
int mid;
while (left <= right) {
mid = left + ((right - left) >> 1);
if (arr[mid] > arr[mid - 1]) {
right = mid - 1;
} else if (arr[mid] > arr[mid + 1]) {
left = mid + 1;
} else {
return mid;
}
}
return -1;
}
/**
* 判断一个元素是否是局部最小值
* @param arr 数组
* @param index 下标
* @return 是否是局部最小值
*/
public static boolean isRight(int[] arr, int index) {
if (arr.length <= 1) {
return true;
}
if (index == 0) {
return arr[index] < arr[index + 1];
}
if (index == arr.length - 1) {
return arr[index] < arr[index - 1];
}
return arr[index] < arr[index - 1] && arr[index] < arr[index + 1];
}
public static void main(String[] args) {
int testTime = 500000;
int maxSize = 50;
int maxValue = 100;
for (int i = 0; i < testTime; i++) {
int[] arr = generateRandomArrayWithNoAdjacent(maxSize, maxValue);
int ans = getLessIndex(arr);
if (!isRight(arr, ans)) {
System.out.println("arr = " + Arrays.toString(arr));
System.out.println("ans = " + ans);
System.out.println("arr[ans] = " + arr[ans]);
System.out.println("error !");
return;
}
}
System.out.println("Test ok!");
}
}
工具类:
package com.zhang.chapter01.util;
import java.util.Arrays;
/**
* @author zhang
* @date 2023/11/21 13:43
* @description 随机数组工具类
*/
public class RandomArrayUtil {
/**
* 生成一个随机数组
*
* @param maxSize 数组最大长度
* @param maxValue 数组最大值
* @return 数组
*/
public static int[] generateRandomArray(int maxSize, int maxValue) {
int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
}
return arr;
}
/**
* 生成一个有序的随机数组
* @param maxSize 数组最大长度
* @param maxValue 数组最大值
* @return 数组
*/
public static int[] generateRandomSortedArray(int maxSize, int maxValue) {
int[] arr = generateRandomArray(maxSize, maxValue);
Arrays.sort(arr);
return arr;
}
/**
* 生成一个相邻元素不相等的随机数组
* @param maxSize 数组最大长度
* @param maxValue 数组最大值
* @return 数组
*/
public static int[] generateRandomArrayWithNoAdjacent(int maxSize, int maxValue) {
int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
}
for (int i = 0; i < arr.length - 1; i++) {
while (arr[i] == arr[i + 1]) {
arr[i + 1] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
}
}
return arr;
}
}