二分查找
一、基础版
使用双指针
public static int binarySearch(int[] a, int target) {
int i = 0;
int j = a.length - 1;
while (i <= j) {
int p = (i + j) >>> 1;
if (target < a[p]) {
j = p - 1;
} else if (target > a[p]) {
i = p + 1;
} else {
// 找到目标值索引位置
return p;
}
}
// 未找到目标值索引,则返回-1
return -1;
}
二分查找算法性能
时间复杂度
- 最坏情况: O ( log n ) O(\log n) O(logn)
- 最好情况:如果带查找目标值正好位于中间位置,则循环一次, O ( 1 ) O(1) O(1)
空间复杂度
- 需要常数个指针 i , j , p i, j, p i,j,p ,因此空间复杂度为 O ( 1 ) O(1) O(1)
二、平衡版
public static int binarySearch(int[] a, int target) {
int i = 0;
int j = a.length;
while (1 < j - i) {
int p = (i + j) >>> 1;
if (target < a[p]) {
j = p;
} else {
i = p;
}
}
if (a[i] == target) {
return i;
} else {
return -1;
}
}
-
左闭右开区间, i i i 可能是目标值,而 j j j 指向的不是目标值;
-
不在循环内找出目标值,只在循环结束,比较最后 i i i 指向的值与目标值,即, a [ i ] = = t a r g e t a[i] == target a[i]==target 是否为 t r u e true true ;
-
循环内的比较次数减少;
-
时间复杂度 O ( log n ) O(\log n) O(logn) (最好和最坏情况)
三、java版本
public static int binarySearch(int[] a, int target) {
int low = 0;
int high = a.length - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
if (a[mid] > target) {
j = mid - 1;
} else if (a[mid] < target) {
i = mid + 1;
} else {
return mid;
}
}
return -(low + 1); // 返回-(insertion point + 1) 另外,找不到不能返回0,因为0无法判断是找到,还是未找到
}
四、二分查找 leftMost & rightMost 版本
l e f t M o s t leftMost leftMost 版本
public static int binarySearchLeftMost(int[] a, int target) {
int i = 0;
int j = a.length - 1;
int candidate = -1;
while (i <= j) {
int p = (i + j) >>> 1;
if (target < a[p]) {
j = p - 1;
} else if (target > a[p]) {
i = p + 1;
} else {
// 找到目标值索引位置,但不能确定是目标值最左侧索引
candidate = p;
j = p - 1;
}
}
// 未找到目标值索引,则返回-1
return candidate;
}
改进版本
public static int binarySearchLeftMost(int[] a, int target) {
int i = 0;
int j = a.length - 1;
int candidate = -1;
while (i <= j) {
int p = (i + j) >>> 1;
if (target <= a[p]) {
j = p - 1;
} else {
i = p + 1;
}
}
// 返回大于或等于目标值得最左侧索引位置
return i;
}
return : 返回 ≥ t a r g e t \ge target ≥target 的最左侧索引位置
r i g h t M o s t rightMost rightMost 版本
public static int binarySearchRightMost(int[] a, int target) {
int i = 0;
int j = a.length - 1;
int candidate = -1;
while (i <= j) {
int p = (i + j) >>> 1;
if (target < a[p]) {
j = p - 1;
} else if (target > a[p]) {
i = p + 1;
} else {
// 找到目标值索引位置,不能确定是目标值最右侧索引
candidate = p;
i = p + 1;
}
}
// 未找到目标值索引,则返回-1
return candidate;
}
改进版本
public static int binarySearchRightMost(int[] a, int target) {
int i = 0;
int j = a.length - 1;
int candidate = -1;
while (i <= j) {
int p = (i + j) >>> 1;
if (target < a[p]) {
j = p - 1;
} else {
i = p + 1;
}
}
// 返回小或等于目标值的最右侧索引位置
return i - 1;
}
return : 返回 ≤ t a r g e t \le target ≤target 的最右侧索引位置
五、二分查找应用
5.1 求排名
使用 l e f t m o s t leftmost leftmost 版本
假如,求 4 4 4 的排名, l e f t m o s t ( a , 4 ) + 1 leftmost(a, 4) + 1 leftmost(a,4)+1 , l e f t m o s t ( a , 4 ) leftmost(a, 4) leftmost(a,4) 得到索引位置为2(该索引位置值 a [ 2 ] = 4 a[2] = 4 a[2]=4 ),因为数组索引从0开始,排名则需要加一。
假如,求 5 5 5 的排名, l e f t m o s t ( a , 5 ) + 1 leftmost(a, 5)+1 leftmost(a,5)+1 , l e f t m o s t ( a , 5 ) leftmost(a, 5) leftmost(a,5) 得到大于等于目标值 5 5 5 最左侧索引 5 5 5 (该索引位置值 a [ 5 ] = 7 a[5] = 7 a[5]=7 ),然后加一操作。
5.2 求前任
使用 l e f t m o s t leftmost leftmost 版本
假如,求 4 4 4的前任, l e f t m o s t ( a , 4 ) − 1 leftmost(a, 4)-1 leftmost(a,4)−1 , l e f t m o s t ( a , 4 ) leftmost(a, 4) leftmost(a,4) 得到大于等于目标值 4 4 4 最左侧索引位置2(该索引位置值 a [ 2 ] = 4 a[2] = 4 a[2]=4 ),因为前任则需要减一操作。
假如,求 5 5 5的前任, l e f t m o s t ( a , 5 ) − 1 leftmost(a, 5)-1 leftmost(a,5)−1 , l e f t m o s t ( a , 5 ) leftmost(a, 5) leftmost(a,5) 得到大于等于目标值 5 5 5 最左侧索引位置5(该索引位置值 a [ 5 ] = 7 a[5] = 7 a[5]=7 ),因为前任则需要减一操作。
5.3 求后任
使用 r i g h t m o s t rightmost rightmost 版本
假如,求 4 4 4 的后任, r i g h t m o s t ( a , 4 ) + 1 rightmost(a, 4)+1 rightmost(a,4)+1 , r i g h t m o s t ( a , 4 ) rightmost(a, 4) rightmost(a,4) 得到小于等于目标值 4 4 4 最右侧索引位置4(该索引位置值 a [ 4 ] = 4 a[4] = 4 a[4]=4 ),因为后任则需要加一操作。
假如,求 5 5 5 的后任, r i g h t m o s t ( a , 5 ) + 1 rightmost(a, 5)+1 rightmost(a,5)+1 , r i g h t m o s t ( a , 5 ) rightmost(a, 5) rightmost(a,5)得到小于等于目标值 5 5 5 最右侧索引位置4(该索引位置值 a [ 4 ] = 4 a[4] = 4 a[4]=4 ),因为后任则需要加一操作。
5.4 最近邻居
寻找前任和后任中最小的,即为最小邻居。
假如求 4 4 4 的最近邻居,使用 l e f t m o s t leftmost leftmost 求前任,找到大于等于 4 4 4 最左侧索引位置2,然后减一操作,即 l e f t m o s t ( a , 4 ) − 1 leftmost(a, 4) - 1 leftmost(a,4)−1 ;
使用 r i g h t m o s t rightmost rightmost 求后任,找打小于等于 4 4 4 的最右侧索引位置4,然后加以操作,即 r i g h t m o s t ( a , 4 ) + 1 rightmost(a, 4) + 1 rightmost(a,4)+1 ;
最后在前任与后任之间取最小值,即 M a t h . m i n ( a [ l e f t m o s t ( a , 4 ) − 1 ] , a [ r i g h t m o s t ( a , 4 ) + 1 ] ) Math.min(a[leftmost(a, 4)-1], a[rightmost(a, 4)+1]) Math.min(a[leftmost(a,4)−1],a[rightmost(a,4)+1])
值应为2
5.5 寻找区间
<
4
<4
<4 的区间,使用
l
e
f
t
m
o
s
t
leftmost
leftmost ,找到大于等于
4
4
4 最左侧索引位置,然后减一,区间则为
[
0
,
l
e
f
t
m
o
s
t
(
a
,
4
)
−
1
]
[0, leftmost(a, 4)-1]
[0,leftmost(a,4)−1]
≤
4
\le4
≤4 的区间,使用
r
i
g
h
t
m
o
s
t
rightmost
rightmost ,找到小于等
4
4
4 最右侧索引位置,区间即为
[
0
,
r
i
g
h
t
m
o
s
t
(
a
,
4
)
]
[0, rightmost(a, 4)]
[0,rightmost(a,4)]
>
4
>4
>4 的区间,使用
r
i
g
h
t
m
o
s
t
rightmost
rightmost ,找到小于等于
4
4
4 的最右侧索引位置
4
4
4 , 区间为
[
r
i
g
h
t
m
o
s
t
(
a
,
4
)
+
1
,
a
.
l
e
n
g
t
h
)
[rightmost(a, 4)+1, a.length)
[rightmost(a,4)+1,a.length)
4
<
x
<
7
4<x<7
4<x<7 的区间,使用
r
i
g
h
t
m
o
s
t
rightmost
rightmost 找到小于等于4最右侧索引位置,然后加一,使用
l
e
f
t
m
o
s
t
leftmost
leftmost 找到大于等于7最左侧索引位置,然后减一,即,
[
l
e
f
t
m
o
s
t
(
a
,
4
)
+
1
,
r
i
g
h
t
m
o
s
t
(
a
,
7
)
−
1
]
[leftmost(a, 4) + 1, rightmost(a, 7) - 1]
[leftmost(a,4)+1,rightmost(a,7)−1]
4
≤
x
≤
7
4\le x\le7
4≤x≤7 的区间,使用
l
e
f
t
m
o
s
t
leftmost
leftmost 找到大于等于4最左侧索引位置,使用
r
i
g
h
t
m
o
s
t
rightmost
rightmost找到小于等于7的最右侧索引位置,即
[
l
e
f
t
m
o
s
t
(
a
,
4
)
,
r
i
g
h
t
m
o
s
t
(
a
,
7
)
]
[leftmost(a, 4), rightmost(a, 7)]
[leftmost(a,4),rightmost(a,7)] 。