二分
二分是十分基础的一个算法,但是二分对于有序元素的查找应用效果十分优秀。由于最近在刷LeetCode,有二分的题目,感觉有不少感受,觉得以前对于二分也没有太重视,毕竟有对应的库函数可以使用,借此机会,写一篇博客,记录题目和感受。
二分的关键点
- 元素有序,是能按照一个给定原则排序的
- 明确需要二分达到的目的(例如我要寻找一个确切的一个元素还是一个区间)
- 确定寻找时区间端点的变化,已经结束的条件
搜索某个确切元素
704. 二分查找
这题我想达到最后搜索区间只剩这个要找到的数(如果找得到的话),那么最终结果会是
s
t
a
r
t
=
e
n
d
\qquad\qquad\qquad\qquad\qquad\qquad\qquad start\ =\ end
start = end
那么
s
t
a
r
t
≤
e
n
d
start\ \leq \ end
start ≤ end,作为循环套件
{
e
n
d
=
M
i
d
−
1
n
u
m
s
[
M
i
d
]
>
t
a
r
g
e
t
s
t
a
r
t
=
M
i
d
+
1
n
u
m
s
[
M
i
d
]
<
t
a
r
g
e
t
找
到
数
的
下
标
M
i
d
n
u
m
s
[
M
i
d
]
=
t
a
r
g
e
t
\begin{cases} end\ =\ Mid\ -\ 1 & nums\ [\ Mid\ ]\ >\ target \\ start\ =\ Mid\ +\ 1 & nums\ [\ Mid\ ]\ <\ target \\ 找到数的下标Mid & nums\ [\ Mid\ ]\ =\ target \end{cases}
⎩⎪⎨⎪⎧end = Mid − 1start = Mid + 1找到数的下标Midnums [ Mid ] > targetnums [ Mid ] < targetnums [ Mid ] = target
int search(int* nums, int numsSize, int target){
int start = 0,end = numsSize-1,Mid = (start + end)/2,ans = -1;
while(start<=end){//最后寻找到一个元素,区间最后有一个元素即可
if(nums[Mid]>target)end = Mid-1;//右区间都不符合条件,将右区间整个剔除
else if(nums[Mid]<target) start = Mid + 1;//左区间都不符合条件,将左区间整个剔除
else {//找到
ans = Mid;
break;
}
Mid = (start + end)/2;
}
//找不到了
return ans;
}
。。。
在已有序列中寻找元素,或(元素不存在时)元素可以插入的位置。
35. 搜索插入位置
这题很好想,查找和上一题一样,是常规二分查找。但是最后如果没查找到,就有一些不同。
我们没有提前查找到的时候,有两种情况,区间都只包含1个值了,
[
s
t
a
r
t
(
e
n
d
)
]
[\ start\ (\ end\ )\ ]
[ start ( end ) ].
此时
M
i
d
=
s
t
a
r
t
Mid\ =\ start
Mid = start,若此时元素正是要找的,我们直接返回。若不是,要么小了,
s
t
a
r
t
+
+
start++
start++
要么,大了,
e
n
d
−
−
end--
end−−,无论是要插入的值处于边界,还是中间,都会落在
s
t
a
r
t
start
start上,返回
s
t
a
r
t
start
start即可
int searchInsert(int* nums, int numsSize, int target){
int start = 0,end = numsSize - 1,Mid = start + (end-start)/2;
while(start<=end){
if(nums[Mid]<target){
start = Mid+1;
}
else if(nums[Mid]>target){
end = Mid-1;
}
else{
break;
}
Mid = start + (end-start)/2;
}
if(start>end)Mid = start;
return Mid;
}
寻找一系列相同元素中的第一个元素(类似lower_bound())
278. 第一个错误的版本
这题我们可以在搜索结束的时候,
e
n
d
=
s
t
a
r
t
\qquad\qquad\qquad\qquad\qquad\qquad\qquad end \ =\ start
end = start
其中
e
n
d
end
end对应第一个第一个错误版本,返回结束时的
e
n
d
end
end即可。
分析:
当
i
s
B
a
d
V
e
r
s
i
o
n
(
M
i
d
)
=
=
t
r
u
e
isBadVersion(Mid)==true
isBadVersion(Mid)==true,
e
n
d
=
M
i
d
end=Mid
end=Mid可以确保
e
n
d
end
end的位置可以必是
B
a
d
V
e
r
s
i
o
n
BadVersion
BadVersion
当
i
s
B
a
d
V
e
r
s
i
o
n
(
M
i
d
)
=
=
f
a
l
s
e
isBadVersion(Mid)==false
isBadVersion(Mid)==false,
s
t
a
r
t
=
M
i
d
+
1
start=Mid+1
start=Mid+1可以确保
s
t
a
r
t
start
start的位置可以最多是第一个
B
a
d
V
e
r
s
i
o
n
BadVersion
BadVersion
最终发现在退出是
e
n
d
=
s
t
a
r
t
end \ =\ start
end = start时,必是第一个
B
a
d
V
e
r
s
i
o
n
BadVersion
BadVersion的位置。
// The API isBadVersion is defined for you.
// bool isBadVersion(int version);
int firstBadVersion(int n) {
int start = 1,end = n;//Mid = (start + end)/2;(边界会溢出)
int Mid = start + (end - start)/2;
while(start<end){//控制最后结束时start最多和end重合
if(isBadVersion(Mid)){
end = Mid;//
}
else {
start = Mid+1;//
}
Mid = start + (end - start)/2;
}
return end;
}
此题中如果使用 M i d = ( s t a r t + e n d ) / 2 Mid = (start +end)/2 Mid=(start+end)/2会爆int,可以使用 M i d = s t a r t + ( e n d − s t a r t ) / 2 Mid = start + (end - start)/2 Mid=start+(end−start)/2代替。
寻找要找寻所在区间(lower_bound,upper_bound)
34. 在排序数组中查找元素的第一个和最后一个位置
首先利用二分找到一个满足条件的点的下标,然后以该点位分界线,将当前搜索区间分为两份,分别找该区间第一个目标值和最后一个目标值。同上一题的处理方法,处理第一个值,修改一下,找的最后一个目标值。
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* searchRange(int* nums, int numsSize, int target, int* returnSize){
int start = 0,end = numsSize - 1,Mid = start + (end - start)/2;
while(start<=end){
if(nums[Mid]>target){
end = Mid - 1;
}
else if(nums[Mid]<target){
start = Mid + 1;
}
else{
int st1 = start,en1 = Mid,M1 = st1 + (en1 - st1)/2;
while(st1<en1){
if(nums[M1] < target)st1 = M1 + 1;
else en1 = M1;
M1 = st1 + (en1- st1)/2;
}
start = st1;
int st2 = Mid,en2 = end,M2 = st2 + (en2 - st2)/2;
while(st2<en2){
if(nums[M2]>target)en2 = M2 - 1;
else st2 = M2;
M2 = st2 + (en2 - st2 + 1)/2;
}
end = en2;
break;
}
Mid = start + (end - start)/2;
}
*returnSize=2;
int *ans=(int*)malloc(sizeof(int)*2);
if(start>end){
start = end = -1;
}
ans[0] =start,ans[1] = end;
return ans;
}
练手二分习题:
33. 搜索旋转排序数组
74. 搜索二维矩阵