Skip to content

162. Find Peak Element

from typing import Callable


class Solution:
    """⛰️ Find Peak Element.

    Problem:
    --------
    Given an integer array `nums` where adjacent elements are distinct, return the index of
    any peak element, i.e. an element strictly greater than its neighbors. Treat
    `nums[-1] = nums[n] = -inf`. The algorithm must run in O(log n) time.

    Key Insight:
    ------------
    The array is not sorted, but the predicate `P(i) = nums[i] > nums[i + 1]` ("going
    downhill") still tells us which half contains a peak:
    - If `P(mid)` is False, we are going uphill, so a peak exists to the right of `mid`
      (the slope cannot rise forever, since `nums[n] = -inf`).
    - If `P(mid)` is True, a peak exists at `mid` or to its left.

    The predicate is not globally monotone, but the invariant "a peak exists in
    [low, high]" is preserved by every step, which is all binary search needs.

    Approach:
    ---------
    Run the `first_true` template over the half-open range [0, n - 1):
    - `mid < high <= n - 1`, so `nums[mid + 1]` is always in bounds.
    - If no index satisfies the predicate, the array is strictly increasing and the
      answer is `high = n - 1`, the last element.
    - For `n == 1` the loop never runs and the answer is 0.

    Complexity:
    -----------
    - Time: O(log n)
    - Space: O(1)
    """

    def findPeakElement(self, nums: list[int]) -> int:
        return self.first_true(nums, lambda idx: nums[idx] > nums[idx + 1])

    def first_true(self, nums: list[int], predicate: Callable[[int], bool]) -> int:
        low = 0
        high = len(nums) - 1

        while low < high:
            mid = (low + high) // 2
            if predicate(mid):
                high = mid
            else:
                low = mid + 1
        return low