leetcode 34 二分查找(二)—— 在有重复元素的排序数组中查找

944 阅读3分钟

第一题 在排序数组中查找元素的第一个和最后一个位置

本题关键点

  • 升序
  • 找目标值的开始位置和结束位置

从题目描述可知,输入数组中可能存在重复元素

设要查找的值为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
}

第二题 在排序数组中查找数字

这道题和上一题基本相同,仅返回值不同,此处不再赘述。