二分法:
区分两种写法:
(一):target在闭区间【left,right】中时:right初始值取(len - 1),
当(target < arr[mid])时,right = mid - 1.(因为闭区间一定取不到mid)
(二):target在左闭右开【left,right)中时:right初始值取len,
当(target < arr[mid])时,right = mid .(因为闭区间可能取不到mid)
题目一:在arr中找出目标值的位置
/**
* 二分查找,查找有序数组中是否包含给出的数,如果有返回该位置,没有返回-1
* 停止条件:如果找到,找到该数停止
*/
public static int binarySearch(int[] arr, int target){
if (arr == null) return -1;
int left = 0;
int right = arr.length ; //如果target在左闭右开区间,right初始值应该不能-1
while (left < right){
int mid = left + ((right - left) >> 1);
if(arr[mid] > target){
right = mid;
}else if(arr[mid] < target){
left = mid + 1;
}else{
return mid;
}
}
return -1;
}
题目二:在arr中找出大于等于目标值的最左侧位置
/**
* 二分查找,查找有序数组中 >= 给出数的最左侧的位置(找左边界)
* 停止条件:一直二分到最后,范围内没有数了
*/
public static int leftest(int[] arr, int target){
if(arr == null || arr.length < 1) return -1;
int left = 0;
int right = arr.length - 1; //如果target的在左闭右闭区间,right初始值应该是能取到的len-1
int leftest = -1;
while(left <= right){
int mid = left + ((right - left) >> 1);
if(target > arr[mid]){
left = mid + 1;
}else{
leftest = mid; //!!!!这两句不能调换,如果调换了寻找的区域是不包含target的边界值
right = mid - 1;//!!!
}
}
return leftest;
}
题目三:找出无序数组中的局部最小值(任意两个相邻的数不相等)
/**
* 在无序数组中,任何两个数不相等,使用二分法找出任意一个局部最小
* 1. 0位置 < 1位置(返回0)
* 2. N-2位置 > N-1位置(返回N-1)
* 3. i-1位置 < i位置 < i+1位置(返回i)
*/
public static int searchMin(int[] arr){
if(arr == null || arr.length == 0) return -1;
int len = arr.length;
if(arr.length == 1 || arr[0] < arr[1]) return 0;
if(arr[len - 2] > arr[len - 1]) return len - 1;
int left = 0;
int right = len - 1;
while(left <= right){//开始的时候,除去第一步返回1位置的情况外,状态一定是左右两边向中间聚拢的趋势
int mid = left + ((right - left) >> 1);
//mid=0只有在left和right都为0或着left=0,right=1时。返回0时的情况已经被第二个if判断了,因此只有返回1的情况了
if(mid == 0) return mid + 1;
//如果mid的位置刚好是局部最小,直接返回
if(arr[mid - 1] > arr[mid] && arr[mid] < arr[mid + 1]){
return mid;
}
//如果左半部分包含局部最小,不需要包含left和left+1的关系,因为在之前已经判断过了(之前可能作为mid或者一开始的判断)
else if(arr[mid] > arr[mid - 1]){
right = mid - 1;
}
//如果右半部分包含局部最小+左半部分和右半部分都包含局部最小(两边都可以找)
else {
left = mid + 1;
}
}
return -1;
}
优化流程:特殊的数据状况、特殊的问题求解
对数器:
使用场景:在没有测试环境的情况下,编写的测试自己写的方法是否在正确一种方式
具体方法:将自己写的方法和一个简单正确的方法同时跑大量的不同的数据(先跑小样本试试),如果的到的结果一致,则说明自己的方法正确;如果不一致,则可能是自己的方法出现了问题,也可能是对比方法出现了问题,这时使用较小的数据调试,之后在进行测试。
使用Math.random获取 A,B 闭区间之间的数 ——> (int) (Math.random()*(B-A+1)+A)
以排序为例:
public static void main(String[] args) {
int[] arr = new int[]{3,2,1,1,3,6,9,7};
//使用对数器验证方法的正确性
boolean success = true;
for(int i = 0; i < 50000; i++){
int[] arr1 = getRandomArr(20, 100);
int[] arr2 = copyArray(arr1);
selectSort(arr1);//作为测试算法
bubbleSort(arr2);//作为对比算法
if(!isEqual(arr1, arr2)){
success = false;
break;
}
}
System.out.println(success ? "success" : "fail");
}
/**
* 对数器生成一个随机长度的内容随机的数组
* 对数器就是为了验证自己的方法是否正确,将自己的方法和简单正确的方法同时运行大量的相同的随机数据,看结果是否相同
* 如果相同则此方法正确,如果不相同则有错误,然后缩小数据范围调试,在运行
*/
public static int[] getRandomArr(int maxSize, int maxValue){
int[] arr = new int[(int)Math.random()*(maxSize+1)];
for(int i = 0; i < arr.length; i++){
arr[i] = ((int)Math.random()*(maxValue+1)) - ((int)Math.random()*(maxValue+1));
}
return arr;
}
/**
* 复制数组,重新开辟空间
* @param org
* @return
*/
public static int[] copyArray(int[] org){
int[] arr = new int[org.length];
for(int i = 0; i < org.length; i++){
arr[i] = org[i];
}
return arr;
}
/**
* 比较两个数组的元素是否相同
* @param arr1
* @param arr2
* @return
*/
public static boolean isEqual(int[] arr1, int[] arr2){
if(arr1 == null || arr2 == null || arr1.length != arr2.length){
return false;
}
for(int i = 0; i < arr1.length; i++){
if(arr1[i] != arr2[i]) return false;
}
return true;
}
本来想使用对数器验证局部最小值的 ,但对比方法的结果不能保证与实现方法一定一致。
/**
* 对数器
*/
/**
* 生成随机长度(在给定的最大长度范围内)的随机内容(在给定最大值范围内)数组
* Math.random()——>[0,1)
* (int)是向下取整
*使用random获取 A,B 闭区间之间的数 ——> (int) (Math.random()*(B-A+1)+A)
* @param maxSize: 长度范围[0,maxSize]
* @param maxValue: 数据范围[-maxValue,+maxValue]
* @return
*/
public static int[] getRandomArr(int maxSize, int maxValue){
int[] arr = new int[(int)Math.random()*(maxSize+1)];//数组长度在[0,maxSize]
for(int i = 0; i < arr.length; i++){
arr[i] = (int)(Math.random()*(maxValue+1)) - (int)(Math.random()*(maxValue+1));//数组元素在[0,maxValue-1]
}
return arr;
}
/**
* 为了测试对数器写的简单、复杂度高、保证准确的方法
* 寻找局部最小值的普通方法(但是由于二分法和从头遍历找到的第一个局部最小值可能不是一个。。。)
* @return
*/
public static int ordinaryMethod(int[] arr){
int len = arr.length;
if(arr == null || arr.length == 0) return -1;
if(arr.length == 1) return 0;
if(arr[0] < arr[1]) return 0;
if(arr[0] > arr[1]) return 1;
if(arr[len-2] > arr[len - 1]) return len - 1;
for(int i = 1; i < len - 1; i++){
if(arr[i] < arr[i-1] && arr[i] < arr[i+1]) return i;
}
return -1;
}