第一题 在排序数组中查找元素的第一个和最后一个位置
本题关键点
- 升序
- 找目标值的开始位置和结束位置
从题目描述可知,输入数组中可能存在重复元素。
设要查找的值为target,解题思路有二:
- 找target在数组中的最左出现位置。再找target+1在数组中的最左出现位置。
- 找target在数组中的最左出现位置。再找target在数组中的最右出现位置。
解释一下“左”和“右”:
- 最左出现位置是这样的:从索引0开始遍历,第一个符合条件的位置。
如 [1,2,2,2,5],2的最左出现位置是索引1。 - 最右出现位置是这样的:从最大索引开始遍历,第一个符合条件的位置。
如 [3,3,3,3,9],3的最右出现位置是索引3。
解法一
找target,再找target+1。
func searchRange(nums []int, target int) []int {
l := len(nums)
if l == 0 {
return []int{-1,-1}
}
// 找target的最左出现位置
i, found := searchLeftMost(nums, target)
if !found {
return []int{-1,-1}
}
// 找target+1的最左出现位置
j, _ := searchLeftMost(nums, target+1)
// 是否找到target+1并不重要,j就是i出现的最右位置的右侧相邻位置。
// 当j是数组长度时,j-1依然是一个有效索引。
return []int{i,j-1}
}
// 搜索target在nums中最左出现位置。
// 返回值:
// - 第一个返回值是索引,取值范围是 [0, 数组长度]。
// - 如果找到了,该索引就是target从左往右第一次出现的位置。
// - 如果没找到,该索引就是target按顺序插入的位置。
// - 第二个返回值
// - true-找到
// - false-未找到
func searchLeftMost(nums []int, target int) (int, bool) {
var (
l = len(nums)
low int
high = l-1
)
for low <= high {
mid := low + (high-low)>>1
v := nums[mid]
if v >= target {
// v大于等于target,向左继续找,故减小high。
high = mid-1
} else {
// v小于target,向右继续找,故增大low。
low = mid+1
}
}
// 此处low的取值范围是[0,l]。
// 考虑以下几种情况,
// [1,3,3,3,5],target=3。此处low=1,请读者依据上面的循环自行推演一下。
// [1,3,3,3,5],target=-1。此处low=0,因为low在上面循环中一直未改变。
// [1,3,3,3,5],target=6。此处low=5,因为上面循环中一直是low增大。
// 这里无需low >= 0,因为在上面循环中,low只会不变或增大。
if low < l && nums[low] == target {
return low, true
}
// 如果low不是有效索引,说明没找到。
return low, false
}
解法二
找两次target。在上面解法的基础上,重点说下searchRightMost函数。
func searchRange(nums []int, target int) []int {
l := len(nums)
if l == 0 {
return []int{-1,-1}
}
i, found := searchLeftMost(nums, target)
if !found {
return []int{-1,-1}
}
j, _ := searchRightMost(nums, target)
return []int{i,j}
}
func searchLeftMost(nums []int, target int) (int, bool) {
var (
l = len(nums)
low int
high = l-1
)
for low <= high {
mid := low + (high-low)>>1
v := nums[mid]
if v < target {
low = mid+1
} else {
high = mid-1
}
}
if low < l && nums[low] == target {
return low, true
}
return low, false
}
func searchRightMost(nums []int, target int) (int, bool) {
var (
l = len(nums)
low int
high = l-1
)
for low <= high {
mid := low + (high-low)>>1
v := nums[mid]
if v > target {
// 向左继续找,所以low不变,high减小。
high = mid-1
} else {
// 向右继续找,所以low增大,high不变。
low = mid+1
}
}
// 此处high的取值范围是 [-1,l)。
// 考虑以下几种情况,
// [1,3,3,3,5],target=3。此处high=3,请读者依据上面的循环自行推演一下。
// [1,3,3,3,5],target=-1。此处high=-1,因为上面循环中一直是high减小。
// [1,3,3,3,5],target=6。此处high=4,因为上面循环中high一直未改变。
// 这里无需判断 high < l,因为在上面循环中,high只会不变或减小。
if high > -1 && nums[high] == target {
return high, true
}
return high, false
}
第二题 在排序数组中查找数字
这道题和上一题基本相同,仅返回值不同,此处不再赘述。