二分查找
二分查找的前提是数组有序(后文此数组表示为Array,已经增序排序),其本质就是对半查找。
二分查找去重的思路
总体思路
if array[mid] > target {
// 搜右区间
} else if array[mid] > target {
// 搜左区间
} else {
// 如果要最小索引,搜左区间
// 如果要最大索引,搜右区间
}
关键在于如何处理 array[mid] == target的情况
搜索最小索引的实现
func binarySearchLeft(s []int, a int) int {
i, j := 0, len(s)-1
for i < j {
// 注意看mid的取值,可以避开区间只有两个数字的时候陷入死循环
mid := int(uint(i+j) >> 1)
// 如果要最小索引,搜左区间
// 如果s[mid] > a, 也要搜左区间
if s[mid] >= a {
// 更新有边界j为mid,为什么不更新为mid-1呢?
// 是为了在s[mid] == a的情况下,把mid也包含在下轮的搜索区间内部,
// 从而防止a是唯一一个目标值的时候丢失了它。
j = mid
} else {
i = mid + 1
}
}
if s[i] != a {
return -1
}
return i
}
搜索最大索引的实现
func binarySearchRight(s []int, a int) int {
i, j := 0, len(s)-1
for i < j {
mid := int(uint(i+j)>>1 + 1)
if s[mid] <= a {
i = mid
} else {
j = mid - 1
}
}
if s[i] != a {
return -1
}
return i
}
合并版本的代码实现
理解上面的实现后,就可以来一个全能版实现了
func binarySearch(s []int, target int, left bool) int {
i, j := 0, len(s)-1
// i == j 时候,也就是当前区间只有一个数字,仍然要给i和j一次调整机会
// 因为当 s[mid] == target 的时候,我们调整下轮搜索区间时候,边界不包括mid了
// 那么假设s中只有一个target,则接下来的搜索区间都会逐步逼向target的方向,
// i最终一定等于target的索引-1,
// j一定等于target的索引+1
// 于是i和j在最后一次调整时候,都会等于a的索引,然后退出循环
for i <= j {
mid := int(uint(i+j) >> 1)
if s[mid] < target {
i = mid + 1
} else if s[mid] > target {
j = mid - 1
} else {
// s[mid] == target
if left {
// search left
j = mid - 1
} else {
// search right
i = mid + 1
}
}
}
if i < len(s) && s[i] == target {
return i
}
if j < len(s) && s[j] == target {
return j
}
return -1
}
最后 附上测试代码
func Test_1(t *testing.T) {
// 0 1 2 3 4 5 6 7 8 9 10 11
s := []int{1, 2, 3, 4, 5, 5, 5, 5, 5, 6, 7, 8}
sort.Ints(s)
t.Log(binarySearchLeft(s, 8) == 11)
t.Log(binarySearchLeft(s, 7) == 10)
t.Log(binarySearchLeft(s, 6) == 9)
t.Log(binarySearchLeft(s, 5) == 4)
t.Log(binarySearchLeft(s, 1) == 0)
t.Log(binarySearchRight(s, 8) == 11)
t.Log(binarySearchRight(s, 7) == 10)
t.Log(binarySearchRight(s, 6) == 9)
t.Log(binarySearchRight(s, 5) == 8)
t.Log(binarySearchRight(s, 1) == 0)
s2 := []int{1, 2}
t.Log(binarySearchRight(s2, 2) == 1)
}