题目描述
Given a sorted array of distinct integers and a target value, return the index if the target is found. If not, return the index where it would be if it were inserted in order.
You must write an algorithm with
O
(
l
o
g
n
)
O(logn)
O(logn) runtime complexity.
思路
首先,我们注意到该数组已排序,且题目对时间复杂度有要求,为 O ( l o g n ) O(logn) O(logn)。显然,我们需要用二分查找。但是,需要注意的是,若没有找到目标值,我们要返回目标值待插入的下标。因此,我们需要对二分查找进行一些修改。
二分查找
我们首先来看二分查找。
一个升序数组nums
,我们要查找target
的值为65。
我们声明三个变量left
,right
和mid
。
left
指target
所在区间的最左端元素的数组下标right
指target
所在区间的最右端元素的数组下标mid
指target
所在区间的中间元素的数组下标
m i d = l e f t + r i g h t 2 mid=\frac{left+right}{2} mid=2left+right
显然,nums[mid]
将数组nums
分成左
和右
两个区间。接着,我们将nums[mid]
与target
比较大小,
- 若
nums[mid] > target
,则target
位于左
区间内,调整right
为mid - 1
。 - 若
nums[mid] < target
,则target
位于右
区间内,调整left
为mid + 1
。 - 若
nums[mid] = target
,则恰好找到target
对应的数组下标,返回mid
。
然后,我们重复以上内容,不断缩小区间,直至找到target
(若存在)。
因为数组nums
按升序排列,所以,如果target
在数组中的话,一定在数组首、末元素组成的区间内。因此,我们给left
赋值0
(第一个数组元素的下标),给right
赋值nums_size - 1
(sums_size
为数组元素个数。right
的值意味着最后一个数组元素的下标,此处rihgt
的值为9
)。此时,mid
的值为4
。
left
=0
right
=9
mid
=4
∵
n
u
m
s
[
4
]
=
58
,
t
a
r
g
e
t
=
65
∴
n
u
m
s
[
4
]
<
t
a
r
g
e
t
∴
l
e
f
t
=
m
i
d
+
1
=
5
\begin{align*} &\because nums[4]=58,target=65 \\ &\therefore nums[4]<target \\ &\therefore left=mid+1=5 \end{align*}
∵nums[4]=58,target=65∴nums[4]<target∴left=mid+1=5
left
=5
right
=9
mid
=7
∵
n
u
m
s
[
7
]
=
73
,
t
a
r
g
e
t
=
65
∴
n
u
m
s
[
7
]
>
t
a
r
g
e
t
∴
r
i
g
h
t
=
m
i
d
−
1
=
6
\begin{align*} &\because nums[7]=73,target=65 \\ &\therefore nums[7]>target \\ &\therefore right=mid-1=6 \end{align*}
∵nums[7]=73,target=65∴nums[7]>target∴right=mid−1=6
left
=5
right
=6
mid
=5
∵
n
u
m
s
[
5
]
=
65
,
t
a
r
g
e
t
=
65
∴
n
u
m
s
[
5
]
=
t
a
r
g
e
t
∴
r
e
t
u
r
n
5
\begin{align*} &\because nums[5]=65,target=65 \\ &\therefore nums[5]=target \\ &\therefore return \thickspace 5 \end{align*}
∵nums[5]=65,target=65∴nums[5]=target∴return5
理解了二分查找的思想,我们就能完成代码了。
/*
BinarySearch()实现二分查找
参数:nums -- 数组地址;nums_size -- 数组长度;target -- 目标值
返回:若能找到target,返回对于的下标;若不能,返回-1。
*/
int BinarySearch(int* nums, int nums_size, int target)
{
int left, right, mid;
left = 0; // 第一个数组元素的下标
right = nums_size - 1; // 最后一个数组元素的下标
while (left <= right)
{
mid = left + (right - left) / 2; // 防止溢出int型
if (nums[mid] > target) // target位于左区间
right = mid - 1;
else if (nums[mid] < target) // target位于右区间
left = mid + 1;
else if (nums[mid] == target) // 找到target
return mid;
}
return -1;
}
值得注意的是,我们将mid
的表达式做了一些调整。
原来:
m
i
d
=
l
e
f
t
+
r
i
g
h
t
2
mid=\frac{left+right}{2}
mid=2left+right
现在:
m
i
d
=
l
e
f
t
+
r
i
g
h
t
−
l
e
f
t
2
mid=left+\frac{right-left}{2}
mid=left+2right−left
原因:left
,right
和mid
都是int
型,它们之间的数学运算结果也是int
型。假如数组很大,那么left
和right
的值也可能会很大。因此,
l
e
f
t
+
r
i
g
h
t
left+right
left+right的结果可能会超出int
的最大值,发生溢出。
考虑到溢出的风险,我们修改了表达式,数学运算的结果依然不变。同时
r
i
g
h
t
−
l
e
f
t
right-left
right−left的结果必然小于int
的最大值,避免了溢出的风险。
解决题目
现在,我们回到这道题目上来。如果说target
在nums
中存在,那么与二分查找一样。但是,如果不存在,题目要求我们返回插入位置的下标。
我们将部分代码进行修改:
原代码:
if (nums[mid] > target)
right = mid - 1;
else if (nums[mid] < target)
left = mid + 1;
else if (nums[mid] == target)
return mid;
修改后:
if (nums[mid] >= target)
right = mid - 1;
else
left = mid + 1;
修改后,无论nums
中是否存在target
,程序都会运行到
l
e
f
t
+
1
=
r
i
g
h
t
left+1=right
left+1=right这一步,即left
与right
相邻。
target
存在
仍以上面二分查找的题目为例,target
仍为65
。
程序继续运行,left
最终会来到65
。此时,left
的值即为所求。target
不存在
将target
改为66
。
程序继续运行,left
的值要加一,即target
插入位置的下标。此时,left
的值也为所求。
总结:无论target
是否在数组中,最终left
的值都能满足要求。所以,我们最终返回left
。
完整代码
int searchInsert(int* nums, int numsSize, int target){
int l, r, mid;
l = 0;
r = numsSize - 1;
while (l <= r)
{
mid = l + (r - l) / 2;
if (nums[mid] >= target)
r = mid - 1;
else
l = mid + 1;
}
return l;
}
(完)