Description

給定一個按非遞減順序排列的整數陣列 nums,找出目標值 target 的起始位置和結束位置。如果不存在,回傳 [-1, -1]。要求 O(log n) 時間複雜度。

Example:

Input: nums = [5,7,7,8,8,10], target = 8 Output: [3,4]

Intuition#

分別用兩次二分搜尋找 lower bound(第一個 >= target)和 upper bound(第一個 > target)。

  • 第一次二分找第一個 >= target 的位置(lower bound)
  • 第二次二分找第一個 > target 的位置(upper bound)
  • 結束位置 = upper bound - 1

Approaches#

1: Linear Scan#

  • 概念: 從左找第一個 target,從右找最後一個 target
  • 時間複雜度: O(n)
  • 空間複雜度: O(1)
Linear Scan 程式碼
class Solution {
    fun searchRange(nums: IntArray, target: Int): IntArray {
        var first = -1
        var last = -1
        for (i in nums.indices) {
            if (nums[i] == target) {
                if (first == -1) first = i
                last = i
            }
        }
        return intArrayOf(first, last)
    }
}

⭐2: Two Binary Searches#

  • 概念: 分別二分搜尋 lower bound 和 upper bound
  • 時間複雜度: O(log n)
  • 空間複雜度: O(1)
class Solution {
    fun searchRange(nums: IntArray, target: Int): IntArray {
        val left = lowerBound(nums, target)
        if (left == nums.size || nums[left] != target) {
            return intArrayOf(-1, -1)
        }
        val right = lowerBound(nums, target + 1) - 1
        return intArrayOf(left, right)
    }

    // 找第一個 >= target 的位置
    private fun lowerBound(nums: IntArray, target: Int): Int {
        var lo = 0
        var hi = nums.size
        while (lo < hi) {
            val mid = lo + (hi - lo) / 2
            if (nums[mid] < target) {
                lo = mid + 1
            } else {
                hi = mid
            }
        }
        return lo
    }
}

巧妙地用 lowerBound(target + 1) - 1 取代 upper bound 的實作,只需要一個通用的 lower bound 函式。

🔑 Takeaways#

  • Pattern: Lower Bound / Upper Bound 二分搜尋
  • 關鍵技巧: lowerBound(target) 找起始位置,lowerBound(target + 1) - 1 找結束位置。這個技巧讓你只需要維護一個 lower bound 模板就能解決各種邊界問題