二分法
二分法存在多种写法!!!
Alg.1 : [left, right]
查找数组中某一个数据 data,存在返回下标,不存在返回 -1;
public int binary(int[] arr, int target) {
int min = 0;
int max = arr.length - 1; // 1. max 的取值
int mid;
while (min <= max) { // 2. while的判断
mid = (min + max) / 2; // 3. 中间值的计算
if (arr[mid] > target) {
max = mid - 1; // 4. min 下一个值的变化
} else if (arr[mid] < target) {
min = mid + 1; // 5. max 下一个值的变化
} else {
return mid; // 6. 返回值的情况
}
}
return -1;
}
- max 的取值
a. max = arr.length - 1;
b. max = arr.length;
区别是:前者相当于两端都闭区间 [left, right],后者相当于左闭右开区间 [left, right),因为索引大小为 nums.length 是越界的。在这儿选择 max = arr.length - 1; ,则在第二步一般应该选择 min <= max。
- while的判断
a. min <= max
b. min < max
while(left <= right) 的终止条件是 left == right + 1,写成区间的形式就是 [right + 1, right]。只有这样区间中才没有数据。
或者带个具体的数字进去 [3, 2],可见这时候区间为空,因为没有数字既大于等于 3 又小于等于 2 的吧。所以这时候 while 循环终止是正确的,直接返回 -1 即可。
while(left < right) 的终止条件是 left == right,写成区间的形式就是 [left, right],或者带个具体的数字进去 [2, 2],这时候区间非空,还有一个数 2,但此时 while 循环终止了。也就是说这区间 [2, 2] 被漏掉了,索引 2 没有被搜索,如果这时候直接返回 -1 就是错误的。
1 和 2 具有相关性。
- 中间值的计算
a. (max + min) / 2; //可能越界
b. min + (max - min) / 2;
c. min + ((max - min) >>> 1); - min 下一个值的变化
a. min =mid +1; - max 下一个值的变化
a. max =mid -1;
b. max =mid ;
本算法的搜索区间是两端都闭的,即 [left, right]。那么当我们发现索引 mid 不是要找的 target 时,下一步应该去搜索 [left, mid-1] 或者 [mid+1, right],因为 mid 已经搜索过,应该从搜索区间中去除。
- 返回值的情况
Alg.2 : 左侧边界(有序数组中小于 target 的数的数目)
查找数组中某一个数据 data左侧边界,存在返回下标,不存在返回 -1;
比如:在 [1 2 2 2 3] 查找 target = 2 的出现在最左边的位置,这儿应该是下标 1;
这儿还可以有另一种理解:有序数组中小于 target 的数的数目。
int left_bound(int[] nums, int target) {
if (nums.length == 0) return -1;
int left = 0;
int right = nums.length-1;
while (left <= right) { // 注意
int mid = (left + right) / 2;
if (nums[mid] == target) {
right = mid-1; // 1. 关键
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid-1; // 注意
}
}
if(left >= nums.length ) // 2. left 越界(不存在target), 但是数组中比target小的值有 left 个
return left;
if(nums[left] != target) // 3. 这时候返回的含义是:不存在target,但是数组中比target小的值有 left 个
return left;
return left;
// 4. right = left -1
}
if (nums[mid] == target) { right = mid-1; }
找到 target 时没有马上返回,而是缩小「搜索区间」的上界 right,在区间 [left, mid]中继续搜索,即不断向左收缩,达到锁定左侧边界的目的。
Alg.3 : [left, right)
查找数组中某一个数据 data,存在返回下标,不存在返回 -1;
public int binary(int[] nums, int target) {
int min = 0;
int max = nums.length; // 1. max 的取值
int mid;
while (min < max) { // 2. while的判断
mid = (min + max) / 2; // 3. 中间值的计算
if (nums[mid] > target) {
max = mid; // 4. min 下一个值的变化
} else if (nums[mid] < target) {
min = mid + 1; // 5. max 下一个值的变化
} else {
return mid; // 6. 返回值的情况
}
}
return -1;
}
- max 的取值
a. max = arr.length - 1;
b. max = arr.length;
区别是:前者相当于两端都闭区间 [left, right],后者相当于左闭右开区间 [left, right),因为索引大小为 nums.length 是越界的。在这儿选择 max = arr.length; ,则在第二步一般应该选择 min < max。
- while的判断
a. min <= max
b. min < max
while(left < right) 的终止条件是 left == right,写成区间的形式就是 [left, right],或者带个具体的数字进去 [2, 2],这时候区间非空,还有一个数 2,但此时 while 循环终止了。也就是说这区间 [2, 2] 被漏掉了,索引 2 没有被搜索,如果这时候直接返回 -1 就是错误的。
- 中间值的计算
a. (max + min) / 2; //可能越界
b. min + (max - min) / 2;
c. min + ((max - min) >>> 1); - min 下一个值的变化
a. min =mid +1; - max 下一个值的变化
a. max =mid -1;
b. max =mid ;
本算法的搜索区间是两端都闭的,即 [left, right]。那么当我们发现索引 mid 不是要找的 target 时,下一步应该去搜索 [left, mid) 或者 [mid+1, right),因为 mid 已经搜索过,应该从搜索区间中去除。
算法实例
完全有序
查找某位数字位置
查找第一个 Or 最后一个出现的某个元素(该元素存在 Or 不存在);
旋转数组
寻找最小值(迭代的寻找无序的一边)
查找目标元素(是否重复)
package com.company.Sort;
public class BinarySearch {
public static void main(String[] args) {
BinarySearch binarySearch = new BinarySearch();
int[] arr = {1,2,5,7,8,9};
int[] arr_cyc = {9,10,1,2,5,7,8};
// System.out.println(binarySearch.binary(arr,3));
// System.out.println(binarySearch.binSearchWithTarget(arr_cyc, 7));
// System.out.println(binarySearch.search(arr_cyc, 11));
int[] arr_cycrep = {2,5,6,0,0,1,2};
System.out.println(binarySearch.binSearchWithTargetAndRepeat(arr_cycrep, 2));
}
/***
* 旋转数组中 目标元素 的下标(位置)
* 重复元素
* */
public int binSearchWithTargetAndRepeat(int[] nums, int target){
int start = 0, end = nums.length-1;
while(start <= end)
{
int mid = start + ((end - start) >>> 1);
// 1. 判断是否有序
if(target == nums[mid]) { // 中间 [mid, mid]
return mid;
}
if(nums[mid] == nums[start] )
{
start++;
continue;
}
if(nums[mid] == nums[end])
{
end--;
continue;
}
if( nums[mid] <= nums[end]) { // 右边有序 (mid, end] ,左边无序
if(target > nums[mid] && target <= nums[end] ) // target 在有序区间内(右边)
{
start = mid+1;
}else {//if(target < nums[mid] || target > nums[end]){ // target 在有序区间外(左边)
end = mid -1;
}
}else if(nums[mid] > nums[end]){ // 右边无序 (mid, end] ,左边有序
if(target >= nums[start] && target < nums[mid] ){ // target 在无序区间内(右边)
end = mid-1;
}else {//if(target < nums[start] || target > nums[mid]){
start = mid + 1; // target 在无序区间外(左边)
}
}
}
return -1;
}
/***
* 旋转数组中 目标元素 的下标(位置)
* 无重复元素
* */
public int binSearchWithTarget(int[] nums, int target){
int start = 0, end = nums.length-1;
while(start <= end)
{
int mid = start + ((end - start) >>> 1);
// 1. 判断是否有序
if(target == nums[mid]) { // 中间 [mid, mid]
return mid;
}
else if( nums[mid] <= nums[end]) { // 右边有序 (mid, end] ,左边无序
if(target > nums[mid] && target <= nums[end] ) // target 在有序区间内(右边)
{
start = mid+1;
}else {//if(target < nums[mid] || target > nums[end]){ // target 在有序区间外(左边)
end = mid -1;
}
}else if(nums[mid] > nums[end]){ // 右边无序 (mid, end] ,左边有序
if(target >= nums[start] && target < nums[mid] ){ // target 在无序区间内(右边)
end = mid-1;
}else {//if(target < nums[start] || target > nums[mid]){
start = mid + 1; // target 在无序区间外(左边)
}
}
}
return -1;
}
public int binSearchWithTarget2(int[] nums, int target){
int start = 0, end = nums.length-1;
while(start <= end)
{
int mid = start + ((end - start) >>> 1);
// 1. 判断是否有序
if(target == nums[mid]) { // 中间 [mid, mid]
return mid;
}
else if( nums[mid] < nums[nums.length-1]) { // 右边有序 (mid, end] ,左边无序
if(target > nums[mid] && target <= nums[nums.length-1] ) // target 在有序区间内(右边)
{
start = mid+1;
}else{ // target 在有序区间外(左边)
end = mid -1;
}
}else if(nums[mid] > nums[nums.length-1]){ // 右边无序 (mid, end] ,左边有序
if(target >= nums[0] && target < nums[mid] ){ // target 在无序区间内(右边)
end = mid-1;
}else{
start = mid + 1; // target 在无序区间外(左边)
}
}
}
return -1;
}
/***
* 旋转数组中最小元素的下标(位置)
* */
public int binSearchWithMin(int[] nums){
int start = 0, end = nums.length-1;
while(start < end)
{
// ------------------------------------
// 优化代码
if(nums[start] <= nums[end])
return start;
// ------------------------------------
int mid = start + ((end - start) >>> 1);
if(nums[mid] <= nums[end])
end = mid;
else{
start = mid+1;
}
}
return start;
}
/**
* 存在重复元素的情况:
* 查找第一次 Or 最后一次 出现的目标元素索引;
* 或者说: 获取小于 / 大于 目标元素的数目
* */
public int binSearchWithRepeat (int[] nums, int target) {
// write code here
if(nums == null || nums.length == 0)
return -1;
int start = 0, end = nums.length-1;
while(start < end)
{
int mid = start + ((end - start) >>> 1);
if(target <= nums[mid])
end = mid;
else if(nums[mid] < target)
start = mid+1;
}
System.out.println(start +" "+ nums[start]+" "+ end);
return nums[start] == target ? start : -1;
}
/**
* 无重复元素: 普通二分
* */
public int binSearch(int[] arr, int target) {
int min = 0;
int max = arr.length - 1; // 1. max 的取值
int mid;
while (min <= max) { // 2. while的判断
mid = (min + max) / 2; // 3. 中间值的计算
if (arr[mid] > target) {
max = mid - 1; // 4. min 下一个值的变化
} else if (arr[mid] < target) {
min = mid + 1; // 5. max 下一个值的变化
} else {
return mid; // 6. 返回值的情况
}
}
return -1;
}
}