基础二分
/**
* 要求数组有序 ,二分法
* @param arr
* @param
* @return
*/
private static int binarySearch(int[] arr, int left, int right, int findVal) {
//给一个出口
if (left >= right) {
return -1;
}
int mid = (left + right) / 2;
int midV = arr[mid];
if (midV < findVal) {
return binarySearch(arr, mid + 1, right, findVal);
} else if (midV > findVal) {
return binarySearch(arr, left, mid - 1, findVal);
} else {
return mid;
}
}
这里有一个讲究的地方:
如果用mid=(left+right)/2,在运行二分查找程序时可能溢出超时。
因为如果left和right相加超过int表示的最大范围时就会溢出变为负数。
所以如果想避免溢出,不能使用mid=(left+right)/2,应该使用mid=left+(right-left)/2。
还有的方式:
public int search(int[] nums, int pivot) {
int length = nums.length;
int left = 0;
int right = length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == pivot) return mid;
if (nums[mid] > pivot) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return -1;
}
同时要注意 left<= right 的退出条件,不然存在以下直接退出-1情况:
search(new int[]{1, 2, 3, 4, 5,6}, 4)
寻找旋转排序数组中的最小值
假设按照升序排序的数组在预先未知的某个点上进行了旋转。例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] 。
请找出其中最小的元素。
本题的关键为找间隙,找到那个进行的旋转的间隙位置,其左右就是最小值。
不断逼近间隙,
public int findMin(int[] nums) {
int left = 0;
int right = nums.length - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] > nums[right]) {
left = mid + 1;
} else {
right = mid;
}
}
return nums[left];
}
存在一种找到即退出的优化方案:
public int findMin(int[] nums) {
if (nums.length == 1) {
return nums[0];
}
int left = 0, right = nums.length - 1;
if (nums[right] > nums[0]) {
return nums[0];
}
while (right >= left) {
int mid = left + (right - left) / 2;
if (nums[mid] > nums[mid + 1]) {
return nums[mid + 1];
}
if (nums[mid - 1] > nums[mid]) {
return nums[mid];
}
if (nums[mid] > nums[0]) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
存在重复数据
解析来自leetcode
第一种情况是nums[pivot] < nums[high]。如下图所示,这说明nums[pivot] 是最小值右侧的元素,因此我们可以忽略二分查找区间的右半部分。
第二种情况是 nums[pivot]>nums[high]。如下图所示,这说明 nums[pivot] 是最小值左侧的元素,因此我们可以忽略二分查找区间的左半部分。
第三种情况是nums[pivot]==nums[high]。如下图所示,由于重复元素的存在,我们并不能确定 nums[pivot] 究竟在最小值的左侧还是右侧,因此我们不能莽撞地忽略某一部分的元素。我们唯一可以知道的是,由于它们的值相同,所以无论 nums[high] 是不是最小值,都有一个它的「替代品」nums[pivot],因此我们可以忽略二分查找区间的右端点。
public int findMin(int[] nums) {
int low = 0;
int high = nums.length - 1;
while (low < high) {
int pivot = low + (high - low) / 2;
if (nums[pivot] < nums[high]) {
high = pivot;
} else if (nums[pivot] > nums[high]) {
low = pivot + 1;
} else {
high -= 1;
}
}
return nums[low];
}