二分搜索
二分查找也称折半查找
(
B
i
n
a
r
y
S
e
a
r
c
h
)
(Binary \;Search)
(BinarySearch),它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。
第一次写博客,之所以选这个算法是因为第一次面试问了这个题,自己学习的时候也没认真看,结果出现了一些问题,所有在这里总结一下。
递归解法
LEETCODE 35. 搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
链接:leetcode-35 搜索插入位置
你可以假设数组中无重复元素。
在面试前,我遇到二分查找是直接用递归来写的,结果面试的时候面试官直接说别用递归,因为递归比较耗资源(函数栈)。但我还是先把递归解法写一下。
class Solution {
public int searchInsert(int[] nums, int target) {
if(target > nums[nums.length-1])
return nums.length;
return searchInsertHepler(nums, target, 0, nums.length - 1);
}
private int searchInsertHepler(int[] nums, int target, int start, int end){
int mid;
if(start == end){
return start;
}
mid = (start + end) / 2;
if(target > nums[mid])
return searchInsertHepler(nums, target, mid + 1, end);
return searchInsertHepler(nums, target, start, mid);
}
}
递归解法时间复杂度 O ( l o g N ) O(logN) O(logN),空间复杂度 O ( l o g N ) O(logN) O(logN)。其实上述用的是尾递归(也是面试的时候才知道的),因此经过编译器优化后空间复杂度也是 O ( 1 ) O(1) O(1),但还是推荐用下面的迭代解法。
迭代解法
迭代解法可以将空间复杂度降到 O ( 1 ) O(1) O(1)
class Solution {
public int searchInsert(int[] nums, int target) {
int i, j, mid;
if(nums.length == 0)
return 0;
i = 0;
j = nums.length - 1;
if(target > nums[j])
return nums.length;
while(j > i){
mid = (i + j) / 2;
if(target > nums[mid])
i = mid + 1;
else
j = mid;
}
return i;
}
}
关于二分查找的一些题目
在排序数组中查找元素的第一个和最后一个位置
给定一个按照升序排列的整数数组 n u m s nums nums,和一个目标值 t a r g e t target target。找出给定目标值在数组中的开始位置和结束位置。
你的算法时间复杂度必须是 O ( l o g N ) O(logN) O(logN) 级别。
如果数组中不存在目标值,返回 [ − 1 , − 1 ] [-1, -1] [−1,−1]。
链接:leetcode-34 在排序数组中查找元素的第一个和最后一个位置
class Solution {
public int[] searchRange(int[] nums, int target) {
int[] answer;
if(nums.length == 0)
return new int[]{-1, -1};
answer = new int[2];
answer[0] = getMinIndex(nums, target);
if(answer[0] == -1){
answer[1] = -1;
}else{
answer[1] = getMaxIndex(nums, target);
}
return answer;
}
private int getMinIndex(int[] nums, int target){
int i, j, mid;
i = 0;
j = nums.length - 1;
while(j > i){
mid = (i + j) / 2;
if(target > nums[mid]){
i = mid + 1;
}else{
j = mid;
}
}
return nums[i] == target ? i : -1;
}
private int getMaxIndex(int[] nums, int target){
int i, j, mid;
i = 0;
j = nums.length - 1;
while(j > i){
mid = (i + j + 1) / 2;
if(target < nums[mid]){
j = mid - 1;
}else{
i = mid;
}
}
return i;
}
}
这道题用了两次二分查找分别找出了数组中 t a r g e t target target 第一次和最后一次出现的位置,两个函数中不同的一个地方是用了
mid = (i + j) / 2;
和
mid = (i + j + 1) / 2;
区别是什么呢?当 [ i , j ] [i, j] [i,j] 的长度是奇数时,两种写法都是区最中间的那个数;不同的是当长度是偶数时,前者是取中间靠左的那个数,后者是取靠右的那个数。
搜索二维矩阵
编写一个高效的算法来判断 m × n m\times n m×n矩阵中,是否存在一个目标值。该矩阵具有如下特性:
每行中的整数从左到右按升序排列。
每行的第一个整数大于前一行的最后一个整数。
示例 1:
输入:
m
a
t
r
i
x
=
[
1
3
5
7
10
11
16
20
23
30
34
50
]
t
a
r
g
e
t
=
3
matrix = \left[ \begin{matrix} 1 & 3 & 5 & 7 \\ 10 & 11 &16&20 \\ 23&30&34&50\\ \end{matrix}\right]\\target = 3
matrix=⎣⎡11023311305163472050⎦⎤target=3
输出:
t
r
u
e
true
true
来源:leetcode-74
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int i, j, k, m, n, mid;
m = matrix.length;
if(m == 0)
return false;
n = matrix[0].length;
if(n == 0)
return false;
if(matrix[0][0] > target)
return false;
i = 0;
j = m - 1;
while(j > i){
mid = (i + j + 1) / 2;
if(target < matrix[mid][0]){
j = mid - 1;
}else{
i = mid;
}
}
k = i;
if(matrix[k][0] == target)
return true;
i = 0;
j = n - 1;
while(j > i){
mid = (i + j + 1) / 2;
if(target < matrix[k][mid]){
j = mid - 1;
}else{
i = mid;
}
}
return matrix[k][i] == target;
}
}
两次二分查找,时间复杂度为 O ( l o g M + l o g N ) = O ( l o g ( M N ) ) O(logM+logN)=O(log(MN)) O(logM+logN)=O(log(MN))