二分查找去重的思路与实现

78 阅读1分钟

二分查找

二分查找的前提是数组有序(后文此数组表示为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)
}