Range Sum of BST
Un árbol de búsqueda binaria se almacena en el arreglo tree en orden por niveles: la raíz está en el índice 0, los hijos del nodo en el índice i están en 2*i+1 (izquierda) y 2*i+2 (derecha), y -1 marca una posición vacía. En un árbol de búsqueda binaria, todos los valores del subárbol izquierdo de un nodo son menores que el valor del nodo, y todos los valores de su subárbol derecho son mayores.
Devuelve la suma de todos los valores de los nodos v que cumplan low ≤ v ≤ high, o 0 si no hay ninguno.
Función
- treeinteger-array
- el árbol de búsqueda binaria en orden por niveles, con -1 para una posición vacía
- lowinteger
- el valor más pequeño que se debe contar
- highinteger
- el valor más grande que contar
- Devuelveinteger
- la suma de los valores de los nodos entre low y high, ambos incluidos
Restricciones
1 ≤ tree.length ≤ 32767- Cada
tree[i]es-1o un valor con0 ≤ tree[i] ≤ 105. tree[0]nunca es-1, así que el árbol tiene al menos un nodo.- El arreglo puede terminar con entradas
-1adicionales después del último nodo. - Ambos hijos de un espacio vacío también están vacíos, y la profundidad es como máximo
14. - El árbol es un árbol binario de búsqueda válido, por lo que todos sus valores son distintos.
0 ≤ low ≤ high ≤ 105- La respuesta cabe en un entero con signo de 32 bits.
Ejemplos
- Entrada
- tree = [20, 8, 31, 3, 12, -1, 40, -1, -1, 10, 15]low = 9high = 31
- Salida
- 88
- Explicación
- Los valores de
9a31son10,12,15,20y31, que suman88. El3, el8y el40quedan fuera del intervalo.
- Entrada
- tree = [50, 25, 75, -1, -1, -1, -1]low = 60high = 70
- Salida
- 0
- Explicación
- El árbol contiene
25,50y75, y ninguno de ellos está entre60y70, así que la suma es0. Las cuatro entradas-1son los espacios vacíos para los hijos de25y75.
- Entrada
- tree = [6, 2, 9, 1, 4, 7]low = 4high = 4
- Salida
- 4
- Explicación
- Con
lowyhighambos en4, solo cuenta un nodo con valor4. El4en el índice4es el hijo derecho de2, así que la respuesta es4.
Para ir más allá
Si tuvieras que responder miles de consultas diferentes (low, high) sobre el mismo árbol, ¿cómo podrías responder cada una en tiempo O(log n)?
Pistas
Ábrelas de una en una. Cada una revela un poco más.
Visitar cada nodo y sumar los valores del rango da la respuesta correcta. ¿Qué te indica el orden del árbol de búsqueda sobre los valores de un nodo?
Todo lo que está en el subárbol izquierdo de un nodo es menor que el nodo, y todo lo que está en su subárbol derecho es mayor. Si el valor del nodo es como máximo
low, ¿puede haber algo a su izquierda que esté dentro del rango?Recorre el árbol con una pila de índices desde la raíz. Añade el valor de un nodo cuando esté dentro del rango, apila su hijo izquierdo en
2*i+1solo cuando el valor sea mayor quelow, y su hijo derecho en2*i+2solo cuando el valor sea menor quehigh.
Solución
Recorrer cada nodo y sumar los valores que están dentro del rango es correcto. El orden del árbol de búsqueda te permite hacer menos: el valor de un nodo te indica en qué lado están los valores menores y los mayores, así que nunca se explora un subárbol que no pueda contener valores dentro del rango.
Visita cada nodo
Intuición
Un hijo existe solo si su índice está dentro del arreglo y el valor allí no es -1. En el primer ejemplo, el 12 del índice 4 tiene 10 y 15 en los índices 9 y 10, y el 31 del índice 2 tiene un espacio izquierdo vacío en el índice 5.
Cada valor del rango se encuentra en algún nodo, así que un recorrido con pila que alcanza todos los nodos y suma los valores que cumplen low ≤ v ≤ high obtiene la suma correcta. Ignora el orden del árbol de búsqueda, así que visita los n nodos, incluso cuando el rango contiene solo unos pocos valores.
Algoritmo
- Coloca el índice raíz
0en una pila y establecetotal = 0. - Extrae un índice
i. Silow ≤ tree[i] ≤ high, sumatree[i]atotal. - Coloca
2*i+1y2*i+2en la pila cuando estén dentro del arreglo y no sean-1. - Cuando la pila esté vacía, devuelve
total.
def rangeSumBST(tree, low, high):
n = len(tree)
total = 0
stack = [0] # node indexes still to visit; the root is never empty
while stack:
i = stack.pop()
value = tree[i]
if low <= value <= high:
total += value
left, right = 2 * i + 1, 2 * i + 2
if left < n and tree[left] != -1:
stack.append(left)
if right < n and tree[right] != -1:
stack.append(right)
return totalPoda según el orden del árbol de búsqueda
Intuición
El subárbol izquierdo de un nodo que contiene v solo tiene valores menores que v. Si v ≤ low, ninguno de ellos está dentro del rango, así que omite ese subárbol. Si v ≥ high, el subárbol derecho solo tiene valores mayores que high, así que omítelo. En el primer ejemplo, 31 es igual a high, por lo que su hijo derecho 40 nunca se apila. 8 está por debajo de low, así que se omite su hijo izquierdo 3, pero se visita su hijo derecho 12, porque los valores entre 8 y 20 pueden estar dentro del rango.
Visitas los k valores del rango, además de los nodos de como máximo dos caminos desde la raíz: tiempo O(h + k) para una profundidad h. Cuando el rango abarca todo el árbol, sigue siendo O(n).
Algoritmo
- Apila el índice raíz
0y establecetotal = 0. - Desapila un índice
iy leev = tree[i]. Silow ≤ v ≤ high, sumavatotal. - Si
v > low, apila el hijo izquierdo2*i+1cuando exista. - Si
v < high, apila el hijo derecho2*i+2cuando exista. - Cuando la pila esté vacía, devuelve
total.
def rangeSumBST(tree, low, high):
n = len(tree)
total = 0
stack = [0] # node indexes still to visit; the root is never empty
while stack:
i = stack.pop()
value = tree[i]
if low <= value <= high:
total += value
left, right = 2 * i + 1, 2 * i + 2
# Smaller values sit on the left, larger on the right: skip a side the range cannot reach.
if value > low and left < n and tree[left] != -1:
stack.append(left)
if value < high and right < n and tree[right] != -1:
stack.append(right)
return total
Errores comunes y casos límite
- Usar comparaciones estrictas. Se cuentan ambos extremos, así que el tercer ejemplo, con
low = high = 4, debe contar el4. - Podar un paso demasiado pronto. En
v = lowse puede descartar el subárbol izquierdo, pero env = low + 1puede contener el propiolow. - Descartar ambos lados de un nodo que está fuera del rango. El
8del primer ejemplo es menor quelow, pero su subárbol derecho contiene10,12y15. - Leer un hijo más allá del final del arreglo. Comprueba
2*i+1 < tree.lengthantes de leer y trata-1como si no hubiera ningún hijo. - En Lua y R, los arreglos empiezan en 1: mantén los índices de los nodos en base 0 para la aritmética de
2*i+1y leetree[i + 1].
Preguntas frecuentes3
¿Cuál es la complejidad temporal de la suma de un rango en un BST?
El recorrido podado visita los k nodos dentro del rango, además de los nodos de como máximo dos rutas desde la raíz: tiempo O(h + k) para un árbol de profundidad h, y O(n) cuando todos los valores están dentro del rango. La pila o la recursión ocupa un espacio de O(h).
¿Se puede resolver la suma del rango de un BST con un recorrido en orden?
Sí. Un recorrido en orden enumera los valores en orden creciente, así que agregas los valores cuando alcanzan low y te detienes en el primero que supera high. La detención anticipada ahorra trabajo en el lado derecho del árbol; la poda lo ahorra en ambos lados.
¿Deberías usar recursión o una pila para la suma de un rango en un BST?
Ambos funcionan. Aquí la profundidad es como máximo 14, así que la recursión se mantiene poco profunda. Una pila explícita evita el límite de recursión en un árbol alto con miles de niveles.
Problemas similares
Problemas que usan las mismas ideas. Resolver dos o tres es lo que fija un patrón.
def rangeSumBST(tree, low, high):
# Escribe el código aquíPrueba #1
Prueba #2
Prueba #3
Argumentos
tree = [20, 8, 31, 3, 12, -1, 40, -1, -1, 10, 15] low = 9 high = 31
Salida Esperada
88