目录
二分查找法
需求
给定一个升序数组upSortArray,查找目标值target
1)、找到目标值,返回其索引
2)、未找到目标值,返回-1
具体实现
java
public static int binarySearch(int[] upSortArray, int target) {
// 参数不合法
if (Objects.isNull(upSortArray) || upSortArray.length == 0) {
throw new IllegalArgumentException("非法参数");
}
// 定义low和high指针(首尾指针)
int low = 0;
int high = upSortArray.length - 1;
// 只要low指针不大于high指针,就一直缩小范围比较
while (low <= high) {
// 取中间索引
int mid = (low + high) >>> 1;
// 中间索引位置的值
int midVal = upSortArray[mid];
if (midVal < target) {// 目标值比中间值大
// 将low指针置于中间位置后面的索引(舍弃中间值左边的所有元素)
low = mid + 1;
} else if (target < midVal) {// 目标值比中间值小
// 将high指针置于中间位置前面的索引(舍弃中间值右边的所有元素)
high = mid - 1;
} else {// 目标值等于中间值
// 找到了目标值target,返回索引
return mid;
}
}
// 未找到目标值,返回-1
return -1;
}
LeetCode-704题
题解
java
class Solution {
public int search(int[] nums, int target) {
// 参数不合法
if (Objects.isNull(nums) || nums.length == 0) {
throw new IllegalArgumentException("非法参数");
}
// 定义low和high指针(首尾指针)
int low = 0;
int high = nums.length - 1;
// 只要low指针不大于high指针,就一直缩小范围比较
while (low <= high) {
// 取中间索引
int mid = (low + high) >>> 1;
// 中间索引位置的值
int midVal = nums[mid];
if (midVal < target) {// 目标值比中间值大
// 将low指针置于中间位置后面的索引(舍弃中间值左边的所有元素)
low = mid + 1;
} else if (target < midVal) {// 目标值比中间值小
// 将high指针置于中间位置前面的索引(舍弃中间值右边的所有元素)
high = mid - 1;
} else {// 目标值等于中间值
// 找到了目标值target,返回索引
return mid;
}
}
// 未找到目标值,返回-1
return -1;
}
}
测试结果

时间复杂度
常见的几种时间复杂度
按照时间复杂度从好到差依次排列如下
- O(1):常量时间,算法时间不会随着数据规模而改变
- O(log(n)):对数时间
- O(n):线性时间,算法时间和数据规模成正比
- O(n*log(n)):拟线性时间
- O(n^2):平方时间
- O(2^n):指数时间
- O(n!):阶乘时间
二分查找法的时间复杂度
O(log(n))对数时间
Java中的二分查找
查看源码
Java中Arrays类的binarySearch方法有很多重载形式,这里查看参数类型为int的方法
java
package java.util;
// ...
public class Arrays {
// ...
public static int binarySearch(int[] a, int key) {
return binarySearch0(a, 0, a.length, key);
}
private static int binarySearch0(int[] a, int fromIndex, int toIndex,
int key) {
int low = fromIndex;
int high = toIndex - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
int midVal = a[mid];
if (midVal < key)
low = mid + 1;
else if (midVal > key)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found.
}
// ...
}
分析源码上的注释
这里只分析部分有关返回值的注释,大概意思如下
- 如果搜索的值包含在数组中,则返回其索引
- 否则,返回(-(插入点)-1)
java
/**
* @return index of the search key, if it is contained in the array;
* otherwise, (-(insertion point) - 1).
*/
public static int binarySearch(int[] a, int key) {
return binarySearch0(a, 0, a.length, key);
}
拓展
通过注释的分析可知,low即为插入点(因为返回值为-(low+1)),这里可以做一下扩展,当知道插入点后,就可以实现需求:如果目标值不存在,则将目标值插入数组中,保持原来数组的顺序
需求实现
java
package algorithm.search;
import java.util.Arrays;
import java.util.Objects;
public class Expand {
public static void main(String[] args) {
// 给定一个升序数组upSortArray和一个目标值target
int[] upSortArray = {12, 20, 22, 34, 36, 40, 43, 46, 46, 55, 60, 78, 80, 83, 83, 99};
int target = 40; // 这里可以分别定义一些存在和不存在的target看测试结果
//int target = 10;
//int target = 66;
//int target = 100;
// 获取返回值
int resultVal = Arrays.binarySearch(upSortArray, target);
// 如果返回值为非负数,为target目标值在数组upSortArray中的索引位置
if (resultVal >= 0) {
System.out.println("目标值[" + target + "]存在于数组中,索引位置为:" + resultVal);
return;
}
System.out.println("数组中不包含目标值:[" + target + "],现将目标值插入数组中...");
// 如果返回值为负数,为(-(插入点)-1)
int insertPoint = Math.abs(resultVal + 1);// 这里通过(+1然后取绝对值)来算出插入点
System.out.println("老数组:" + Arrays.toString(upSortArray));
// 计算出插入点,将target目标值插入正确的位置,形成一个新数组
upSortArray = handleInsertTargetToArray(upSortArray, target, insertPoint);
System.out.println("新数组:" + Arrays.toString(upSortArray));
}
/**
* 处理插入目标值,返回新数组
*/
public static int[] handleInsertTargetToArray(int[] array, int target, int insertPoint) {
// 参数校验
if (Objects.isNull(array)) throw new IllegalArgumentException("数组不合法");
if (insertPoint < 0) throw new IllegalArgumentException("插入点不合法");
// 新数组长度
int newArrayLen = array.length + 1;
// 定义新数组
int[] newArray = new int[newArrayLen];
// 将插入点之前的数据拷贝到新数组中
System.arraycopy(array, 0, newArray, 0, insertPoint);
// 将目标值放入新数组中
newArray[insertPoint] = target;
// 将插入点之后的数据拷贝到新数组中
System.arraycopy(array, insertPoint, newArray, (insertPoint + 1), (array.length - insertPoint));
return newArray;
}
}
测试结果
- target = 40
bash
目标值[40]存在于数组中,索引位置为:5
- target = 10
bash
数组中不包含目标值:[10],现将目标值插入数组中...
老数组:[12, 20, 22, 34, 36, 40, 43, 46, 46, 55, 60, 78, 80, 83, 83, 99]
新数组:[10, 12, 20, 22, 34, 36, 40, 43, 46, 46, 55, 60, 78, 80, 83, 83, 99]
- target = 66
bash
数组中不包含目标值:[66],现将目标值插入数组中...
老数组:[12, 20, 22, 34, 36, 40, 43, 46, 46, 55, 60, 78, 80, 83, 83, 99]
新数组:[12, 20, 22, 34, 36, 40, 43, 46, 46, 55, 60, 66, 78, 80, 83, 83, 99]
- target = 100
bash
数组中不包含目标值:[100],现将目标值插入数组中...
老数组:[12, 20, 22, 34, 36, 40, 43, 46, 46, 55, 60, 78, 80, 83, 83, 99]
新数组:[12, 20, 22, 34, 36, 40, 43, 46, 46, 55, 60, 78, 80, 83, 83, 99, 100]
LeetCode-35题
题解
方法一
可以使用Java中提供的二分查找来实现这道题,只需要做小小的改动
java
class Solution {
public int searchInsert(int[] nums, int target) {
return binarySearch0(nums, 0, nums.length, target);
}
// 这里基本上完全用的是Java中提供的二分查找算法,只是改动了最后一行
private static int binarySearch0(int[] a, int fromIndex, int toIndex,
int key) {
int low = fromIndex;
int high = toIndex - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
int midVal = a[mid];
if (midVal < key)
low = mid + 1;
else if (midVal > key)
high = mid - 1;
else
return mid; // key found
}
// Java中这里返回的是-(插入点 + 1)
// return -(low + 1); // key not found.
// 本题需要返回的是插入点即可
return low;
}
}
方法二
使用二分查找法的LeftMost (如数组中存在一些重复的target值,通过LeftMost算法可以找到最左侧的target值所在的索引)
java
class Solution {
public int searchInsert(int[] nums, int target) {
int low = 0;
int high = nums.length - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
int midVal = nums[mid];
// 这里做出了改变,当遇到第一个target值时,继续像左找
if (target <= midVal) {
high = mid - 1;
} else {
low = mid + 1;
}
}
return low;
}
}
测试结果
方法一

方法二

LeetCode-34题
题解
java
class Solution {
public int[] searchRange(int[] nums, int target) {
// 参数校验
if (Objects.isNull(nums) || nums.length == 0) {
return buildNoTargetResult();
}
// 初始化返回结果
int[] result = new int[2];
// 获取数组中最左侧的target值所在下标
int leftMost = targetIndexLeftMost(nums, target);
// 数组中不存在target值
if (leftMost < 0) {
return buildNoTargetResult();
}
// 设置返回结果的左边界
result[0] = leftMost;
// 获取数组中最右侧的target值所在下标
int rightMost = targetIndexRightMost(nums, target, leftMost + 1);
// 最左侧之后再无target值出现
if (rightMost < 0) {
result[1] = leftMost;
return result;
}
// 设置返回结果的右边界
result[1] = rightMost;
return result;
}
/**
* 数组中无目标值
*/
public int[] buildNoTargetResult() {
return new int[] { -1, -1 };
}
/**
* target值出现在数组中最左侧的index
*/
public int targetIndexLeftMost(int[] nums, int target) {
int low = 0;
int high = nums.length - 1;
// 记录target在数组中的最左侧下标
int leftMost = -1;
while (low <= high) {
int mid = (low + high) >>> 1;
int midVal = nums[mid];
if (target < midVal) {
high = mid - 1;
} else if (midVal < target) {
low = mid + 1;
} else {
// 更新最左下标
leftMost = mid;
// 这里不停止寻找,继续向左寻找是否还有目标值
high = mid - 1;
}
}
return leftMost;
}
/**
* target值出现在数组中最右侧的index
*/
public int targetIndexRightMost(int[] nums, int target, int low) {
// 目标值最左侧为数组最后一个元素
if (low == nums.length) {
return -1;
}
int high = nums.length - 1;
// 记录target在数组中的最左侧下标
int rightMost = -1;
while (low <= high) {
int mid = (low + high) >>> 1;
int midVal = nums[mid];
if (target < midVal) {
high = mid - 1;
} else if (midVal < target) {
low = mid + 1;
} else {
// 更新最右下标
rightMost = mid;
// 这里不停止寻找,继续向右寻找是否还有目标值
low = mid + 1;
}
}
return rightMost;
}
}
测试结果
