Description

給定已排序的整數陣列 arr、整數 k 和整數 x,回傳最接近 xk 個元素,以排序方式回傳。若兩個元素距離相同,較小的優先。

Example:

Input: arr = [1,2,3,4,5], k = 4, x = 3 Output: [1,2,3,4]

Intuition#

核心思路:結果一定是原陣列中連續的 k 個元素。用二分搜尋找到最佳的起始位置。

  • 因為陣列已排序,最接近 x 的 k 個元素一定是連續的子陣列
  • 可以用 Two Pointers 從兩端縮小,或用 Binary Search 找起始位置
  • Binary Search 的搜尋目標:找到 left 使得 arr[left:left+k] 最佳

Approaches#

1: Two Pointers(從兩端收縮)#

  • 概念: 先取整個陣列,每次比較兩端哪個離 x 更遠,移除較遠的那端,直到剩下 k 個
  • 時間複雜度: O(n) - 最多移除 n-k 次
  • 空間複雜度: O(1) - 不計結果
class Solution {
    fun findClosestElements(arr: IntArray, k: Int, x: Int): List<Int> {
        var left = 0
        var right = arr.size - 1
        while (right - left + 1 > k) {
            if (x - arr[left] <= arr[right] - x) {
                right--
            } else {
                left++
            }
        }
        return arr.slice(left..right)
    }
}

⭐2: Binary Search 找起始位置#

  • 概念: 二分搜尋找起始索引 left(範圍 [0, n-k])。比較 x - arr[mid]arr[mid+k] - x 決定搜尋方向
  • 時間複雜度: O(log(n-k) + k) - 二分 + 建立結果
  • 空間複雜度: O(1) - 不計結果
class Solution {
    fun findClosestElements(arr: IntArray, k: Int, x: Int): List<Int> {
        var left = 0
        var right = arr.size - k

        while (left < right) {
            val mid = left + (right - left) / 2
            // 比較視窗左端點和右端點之外的距離
            if (x - arr[mid] > arr[mid + k] - x) {
                left = mid + 1
            } else {
                right = mid
            }
        }
        return arr.slice(left until left + k)
    }
}

比較的是 x - arr[mid]arr[mid+k] - x(注意是 mid+k 不是 mid+k-1),因為我們在比較「是否應該把視窗右移」– 即把 arr[mid] 換成 arr[mid+k] 是否更好。

🔑 Takeaways#

  • Pattern: Binary Search 找最佳視窗起始位置
  • 關鍵技巧: 搜尋空間是起始位置 [0, n-k];比較對象是 arr[mid]arr[mid+k](視窗外下一個元素),決定視窗是否應該右移