二分搜索大家都很熟悉,首先我们先来看看基本框架
func binarySearch(nums []int, target int) int {left, right := 0, ...for ... {mid := left + (right-left)/2if nums[mid] == target {...} else if nums[mid] < target {left = ...} else if nums[mid] > target {right = ...}}return ...
}
看完代码之后我们会发现:
- 数组得是有序的才能成立
- 在存在两个及以上的答案时,只能找出一个,至于下标的情况我觉得可以自定义
- 如果数组长度为偶数,初始化时mid在中间偏左的那一格
接下来在实战中看:
寻找一个数
这可以说是最简单的内容
func binarySearch(nums []int, target int) int {left := 0 //right := len(nums) - 1 for left <= right {mid := left + (right - left) / 2 if nums[mid] == target { return mid} else if nums[mid] < target { left = mid + 1 // [mid + 1, right]} else if nums[mid] > target { // 目标值在左半部分,注意right = mid - 1 // [left, mid - 1]}}return -1 // 未找到
}
这里需要注意一点,为何是 for left <= right
而不是 for left < right
?
我们可以先从最极端的情况入手,假设要寻找的数在最后一个,那么在前一步应该是 left = right - 1
, mid = left
,然后 left = mid + 1 = right
,在进行循环时,循环就会进不去。
从理论上解释,因为在之前设定值时,数组为闭区间 [left, right]
,所以得保证边界值,也就是说,循环的条件设定和搜索区间设定是相关联的
寻找左侧边界
那么接下来看一下右侧开区间的做法:
func leftBound(nums []int, target int) int {left := 0right := len(nums)for left < right {mid := left + (right - left) / 2if nums[mid] == target {right = mid} else if nums[mid] < target {left = mid + 1 // [mid + 1, right)} else if nums[mid] > target {right = mid // [left, mid)}}return left
}
不难发现,随着区间的修改,在对应条件下的操作也会对应转换。
在这里阐述一点我的个人想法,二分查找的最大特点在于区间设定,而在对应条件下做出的操作有点像递归,基本盘并没有改变
寻找右侧边界
func right_bound(nums []int, target int) int {left, right := 0, len(nums)for left < right {mid := left + (right-left)/2if nums[mid] == target { left = mid + 1 } else if nums[mid] < target {left = mid + 1} else if nums[mid] > target {right = mid}}return left - 1
}
这里面的精华在于
if nums[mid] == target { left = mid + 1
他并没有直接的收网,而是继续直到最右侧的诞生,这也得益于mid的设置,可以把他看成以left为基准向前进
实战
我们来看力扣的34. 在排序数组中查找元素的第一个和最后一个位置
直接把方法摆上既可以解决
func searchRange(nums []int, target int) []int {left := leftBound(nums, target)right := rightBound(nums, target)return []int{left, right}
}func leftBound(nums []int, target int) int {left, right := 0, len(nums)-1for left <= right {mid := left + (right - left) / 2if nums[mid] < target {left = mid + 1} else if nums[mid] > target {right = mid - 1} else if nums[mid] == target {// 别返回,锁定左侧边界right = mid - 1}}// 判断 target 是否存在于 nums 中if left < 0 || left >= len(nums) {return -1}// 判断一下 nums[left] 是不是 targetif nums[left] == target {return left}return -1
}func rightBound(nums []int, target int) int {left, right := 0, len(nums)-1for left <= right {mid := left + (right - left) / 2if nums[mid] < target {left = mid + 1} else if nums[mid] > target {right = mid - 1} else if nums[mid] == target {// 别返回,锁定右侧边界left = mid + 1}}// 判断 target 是否存在于 nums 中// if left - 1 < 0 || left - 1 >= len(nums) {// return -1// }if right < 0 || right >= len(nums) {return -1}if nums[right] == target {return right}return -1
}
当然也有另外一种解法,力扣显示这种时间复杂度更低