题目
原文链接:leetcode-cn.com/problems/fi…
读题
- 给一个升序排列的整数数组
- 找出目标值的开始位置和结束位置
- 不存在则返回
[-1,-1] - 尽可能在
时间内解决
解题
- 很明显,这道题是二分法的变形。
- 最简单的是先使用二分法找出目前值的一个位置,然后向两边扩展寻找出边界值。
- 这种方法情况下,如果数组为
[1,1,1,1,...,1,1,1],目标值为1的情况下,会遍历整个数组,效率为 - 一种解决方案是通过两次二分查找来解决,分别找到开始、结束位置。二分法的本质是减少查找范围,尝试能否通过改变判断条件来查找边界值。
go语言+二分法+暴力搜索
package main
import (
"fmt"
)
func searchRange(nums []int, target int) []int {
ans := []int{-1, -1}
targetPos := -1
left, right := 0, len(nums)-1
// 二分法找到target出现的位置
for left <= right {
mid := left + (right-left)/2
if nums[mid] == target {
targetPos = mid
break
} else if nums[mid] < target {
left = mid + 1
} else {
right = mid - 1
}
}
// 没有找到
if targetPos == -1 {
return ans
}
// 找到位置后,向两边扩展找边界,这里极限情况下会有性能问题,比如数组为[8,8,...,8,8,8],target=8的情况
for i := targetPos; i >= 0; i-- {
if nums[i] == target {
ans[0] = i
}
}
for i := targetPos; i < len(nums); i++ {
if nums[i] == target {
ans[1] = i
}
}
return ans
}
func main() {
arr := []int{1, 2, 3, 4, 5, 5, 5, 5, 6, 7, 8, 9, 10}
fmt.Println(searchRange(arr, 5))
}
go+两次二分法解决问题
package main
import (
"fmt"
)
func searchRange(nums []int, target int) []int {
length := len(nums)
start, end := -1, -1
left, right := 0, length-1
// 二分法找到target开始出现的位置
for left <= right {
mid := left + (right-left)/2
if nums[mid] == target {
if mid > 0 && nums[mid-1] == target {
right = mid - 1
} else {
start = mid
break
}
} else if nums[mid] < target {
left = mid + 1
} else {
right = mid - 1
}
}
left, right = 0, length-1
// 二分法找到target最后出现的位置
for left <= right {
mid := left + (right-left)/2
if nums[mid] == target {
if mid < length-1 && nums[mid+1] == target {
left = mid + 1
} else {
end = mid
break
}
} else if nums[mid] < target {
left = mid + 1
} else {
right = mid - 1
}
}
return []int{start, end}
}
func main() {
arr := []int{1, 2, 3, 4, 5, 5, 5, 5, 6, 7, 8, 9, 10}
fmt.Println(searchRange(arr, 5))
}
反思与总结
- 通过本道题对二分法有了进一步的认识。维基百科对二分法的解释如下:
二分法(dichotomy)指的是将一个整体事物分割成两部分。也即是说,这两部分必须是互补事件,即所有事物必须属于双方中的一方,且互斥,即没有事物可以同时属于双方。
之后如果遇到二分法的变形题,我想关键是每次查找将搜索范围减半,要么确定搜索值在哪个半区,要么反其道确定搜索值不在某个半区从而排除掉。