Find Peak Element
El array nums sube y después baja: cada valor es mayor que el anterior hasta cierto índice, y cada valor después de ese índice es menor que el anterior. Cualquiera de las dos partes puede estar vacía, así que el array puede solo subir o solo bajar. El valor en el índice de giro es el pico, el único valor mayor que todos sus vecinos.
Devuelve el índice del pico en tiempo O(log n).
Función
- numsinteger-array
- los valores, subiendo hasta un máximo y después bajando
- Devuelveinteger
- el índice del pico
Restricciones
1 ≤ nums.length ≤ 5000-106 ≤ nums[i] ≤ 106nums[i] != nums[i+1]para cadaiválido.numsaumenta estrictamente hasta un índice y disminuye estrictamente después, así que hay exactamente un pico.
Ejemplos
- Entrada
- nums = [2, 5, 9, 4, 1]
- Salida
- 2
- Explicación
- Los valores suben pasando por 2, 5 y 9, y después bajan pasando por 4 y 1. El cambio de dirección está en 9, índice 2.
- Entrada
- nums = [8, 6, 3, -2]
- Salida
- 0
- Explicación
- El array solo desciende, así que la parte ascendente está vacía y el pico es el primer valor, 8 en el índice 0.
- Entrada
- nums = [-7, -3, 0, 12, 40]
- Salida
- 4
- Explicación
- El arreglo solo aumenta, así que el pico es el último valor, 40 en el índice 4. Los valores negativos no cambian nada.
Para ir más allá
En una cuadrícula donde no hay dos celdas vecinas iguales, un pico es una celda mayor que sus hasta cuatro vecinas. ¿Puedes encontrar uno sin leer todas las celdas?
Pistas
Ábrelas de una en una. Cada una revela un poco más.
Elige cualquier índice
midy observanums[mid+1]. ¿Qué te indica la dirección de ese paso sobre dónde está el pico?Un paso hacia arriba solo ocurre antes del pico, y un paso hacia abajo solo en el pico o después de este. Así que una comparación descarta un lado de
mid.Mantén un rango que siempre contenga el pico. En un paso hacia arriba, mueve
loamid+1; de lo contrario, muevehiamid. Detente cuando coincidan.
Solución
Con un único pico, el pico es el valor más grande, así que una pasada lo encuentra en O(n). Para O(log n), compara nums[mid] con nums[mid+1]: la pendiente en mid te indica en qué lado del pico se encuentra, así que en cada paso puedes descartar la mitad del intervalo.
Camina hasta que los valores empiecen a disminuir
Intuición
Antes del pico, cada valor es menor que el siguiente, y a partir del pico cada valor es mayor. Así que el pico es el primer índice i donde nums[i] > nums[i+1], o el último índice si el arreglo solo aumenta.
Cuando el pico está al final de 5000 valores, el recorrido realiza 4999 comparaciones, mientras que la búsqueda binaria necesita 13.
Algoritmo
- Para
idesde 0 hastan-2, comparanums[i]connums[i+1]. - En el primer
idondenums[i]sea mayor, devuelvei. - Si el bucle termina sin encontrar una caída, devuelve
n-1.
def findPeakElement(nums):
for i in range(len(nums) - 1):
if nums[i] > nums[i + 1]:
return i # the first step down starts at the peak
return len(nums) - 1 # never went down: the last value is the peakBúsqueda binaria en la pendiente
Intuición
Lee un par, nums[mid] y nums[mid+1]. Un paso hacia arriba significa que el arreglo sigue ascendiendo, lo que en este arreglo sucede solo antes del pico: el pico está en mid+1 o a su derecha. Un paso hacia abajo significa que estás en el pico o más allá: el pico está en mid o a su izquierda.
Mantén un intervalo [lo, hi] que siempre contenga el pico. Un paso hacia arriba mueve lo a mid+1. Un paso hacia abajo mueve hi a mid, manteniendo mid porque podría ser el pico. Cuando lo llega a hi, el índice que queda es el pico. Redondear mid hacia abajo hace que quede por debajo de hi, así que mid+1 siempre existe.
En el primer ejemplo, mid = 2 ve 9 > 4, así que hi = 2; después, mid = 1 ve 5 < 9, así que lo = 2, el pico. 5000 valores requieren como máximo 13 pasos.
Algoritmo
- Establece
lo = 0yhi = n-1. - Mientras
lo < hi, tomamid = lo + (hi - lo) / 2, redondeado hacia abajo. - Si
nums[mid] < nums[mid+1], establecelo = mid + 1. - De lo contrario, establece
hi = mid. - Devuelve
lo.
def findPeakElement(nums):
lo, hi = 0, len(nums) - 1 # the peak is always inside [lo, hi]
while lo < hi:
mid = lo + (hi - lo) // 2 # mid < hi, so mid + 1 is a valid index
if nums[mid] < nums[mid + 1]:
lo = mid + 1 # still climbing: the peak is to the right of mid
else:
hi = mid # going down: the peak is mid or to its left
return lo
Errores comunes y casos límite
- Asignar
hi = mid - 1al dar un paso hacia abajo. En[2, 5, 9, 4, 1], el primermides el pico mismo, yhi = 1lo descarta; la búsqueda devuelve 1. - Comparar con
nums[mid-1]mientras se redondeamidhacia abajo.midpuede ser 0, donde no hay ningún vecino a la izquierda. - Usar un rango semiabierto con
hi = n. Después,midpuede sern-1, ynums[mid+1]accede a una posición fuera del final, incluso con un único valor. - Olvidar el desplazamiento en Lua y R. La respuesta sigue siendo el índice basado en 0, así que la posición
ise devuelve comoi-1.
Preguntas frecuentes3
¿Cuál es la complejidad temporal de Find Peak Element?
La búsqueda binaria se ejecuta en tiempo O(log n) y espacio O(1), en un máximo de 13 pasos para 5000 valores. El recorrido desde la izquierda es O(n), ya que puede leer cada par antes de encontrar la caída.
¿Sigue funcionando la búsqueda binaria cuando hay varios picos?
Sí, siempre que los vecinos sean distintos y los lugares más allá de ambos extremos cuenten como inferiores a cualquier valor. Devuelve un pico, no siempre el más alto. Después de un ascenso, los valores deben empezar a descender, como muy tarde, al final del rango, y el primer cambio de dirección es un pico, así que el lado que conserva la búsqueda siempre contiene uno.
¿El pico es siempre el valor más grande?
En este problema, sí: el arreglo sube hasta un pico y baja después, así que nada más puede ser mayor. En la versión con varios picos, no es así, ya que cualquier valor mayor que sus dos vecinos cuenta. Por eso la búsqueda compara los vecinos en lugar de buscar el máximo.
Problemas similares
Problemas que usan las mismas ideas. Resolver dos o tres es lo que fija un patrón.
def findPeakElement(nums):
# Escribe el código aquíPrueba #1
Prueba #2
Prueba #3
Argumento
nums = [2, 5, 9, 4, 1]
Salida Esperada
2