二分查找
二分搜索是一个效率很高的算法。一个良好实现的二分搜索算法,其时间复杂度可以达到Θ(log𝑛)
,而空间复杂度只有𝑂(1)
。
要注意二分搜索的适用场景:
- 依赖顺序表结构
- 数据本身必须有序
- 数据量相对比较元素的开销要足够大——不然遍历即可
- 数据量相对内存空间不能太大——不然顺序表装不下
实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
func binarySearch(a []int, t int) int {
l, r := 0, len(a)-1
for l <= r {
mid := l + (r-l)>>1
if a[mid] == t {
return mid
} else if a[mid] < t {
l = mid + 1
} else {
r = mid - 1
}
}
return -1
}
|
二分法技术sqrt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
func sqrt(x float64, p float64) float64 {
l, r := 0.0, x
for {
mid := l + (r-l)/2.0
t := mid * mid
if math.Abs(x-t) < p {
return mid
} else if t < x {
l = mid + 1
} else {
r = mid - 1
}
}
}
|
变体
第一个等于目标值的元素下标
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
func bs_first(a []int, t int) int {
l, r := 0, len(a)-1
for l <= r {
mid := l + (r-l)>>1
if a[mid] == t { // 相等时,右边压缩,找到最左值
r = mid - 1
} else if a[mid] < t {
l = mid + 1
} else {
r = mid - 1
}
}
if l >= len(a) || a[l] != t {
return -1
}
return l
}
|
最后一个等于目标值的元素下标
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
func bs_last(a []int, t int) int {
l, r := 0, len(a)-1
for l <= r {
mid := l + (r-l)>>1
if a[mid] == t { // 相等时,左边压缩,找到最右边
l = mid + 1
} else if a[mid] < t {
l = mid + 1
} else {
r = mid - 1
}
}
if r < 0 || a[r] != t {
return -1
}
return r
}
|
第一个大于目标值的元素下标
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
func binarySearch(a []int, t int) int {
l, r := 0, len(a)-1
for l <= r {
mid := l + (r-l)>>1
if a[mid] == t {
l = mid + 1
} else if a[mid] < t {
l = mid + 1
} else {
r = mid - 1
}
}
if l >= len(a) || a[l] == t {
return -1
}
return l
}
|
第一个不超过目标值的元素下标
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
func binarySearch(a []int, t int) int {
l, r := 0, len(a)-1
for l <= r {
mid := l + (r-l)>>1
if a[mid] == t {
r = mid - 1
} else if a[mid] <= t {
l = mid + 1
} else {
r = mid - 1
}
}
if r < 0 || a[r] == t {
return -1
}
return r
}
|
二分查找变化题目
题目
给定升序数组,从任意位置切一刀后左右互换后构成新的数组,在该数组中查找目标值t的下标
解题思路
分析的核心事如何确定有序数段
代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
func splitSearch(arr []int, t int, i int, j int) int {
for i <= j {
//获取中间值
mid := i + (j-i)>>1
//刚好相等,得到答案
if arr[mid] == t {
return mid
} else if arr[mid] < t {
//mid-j之间有序,则使用正常二分查找
if t <= arr[j] {
i = mid + 1
} else {
j = mid - 1
}
} else {
//i-mid之间有序,使用正常二分查找
if t >= arr[i] {
j = mid - 1
} else {
i = mid + 1
}
}
}
return -1
}
|
题目
LeetCode 300. Longest Increasing Subsequence
解题思路
连续最大子序列,经典的题目,主要思路两种
-
动态规划 dp[i]
表示0到i的数组中,已i为结尾的最大子序列长度,则递归方程为dp[i+1]=max(dp[j]+1),0<=j<=i
-
二分查找+栈
设定一个数组栈,遍历数组,每次通过二分查找寻找p[i]
在数组栈中最小适合位置,如果有则替换,没有则把p[i]
追加,最后答案为数组栈的大小
代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
func lengthOfLIS(nums []int) int {
ret := make([]int, 0, len(nums))
for i := 0; i < len(nums); i++ {
t := find(ret, nums[i])
if t == -1 {
ret = append(ret, nums[i])
} else {
ret[t] = nums[i]
}
}
return len(ret)
}
//找到第一个大于或等于k的位置
func find(nums []int, k int) int {
ll := len(nums) - 1
if ll < 0 {
return -1
}
l, r := 0, ll
for l < r {
mid := l + (r-l)>>1
if nums[mid] == k {
return mid
} else if nums[mid] > k {
r = mid
} else {
l = mid + 1
}
}
if l == ll && nums[l] < k {
return -1
}
return l
}
|
参考
- 谈谈二分搜索及其变体
- 二分查找算法及其扩展