Top View of Binary Tree
Você recebe uma árvore binária armazenada no array root em ordem de heap: a raiz no índice 0, os filhos do índice i nos índices 2*i+1 (à esquerda) e 2*i+2 (à direita), e -1 para uma posição vazia.
A raiz está na coluna 0; um filho à esquerda fica uma coluna à esquerda de seu pai e um filho à direita fica uma coluna à direita. Vendo de cima, você vê o nó mais superficial de cada coluna; em caso de empate, aquele que aparece primeiro em seu nível, da esquerda para a direita (o índice menor). Retorne os valores que você vê, da coluna mais à esquerda até a mais à direita.
Função
- rootinteger-array
- a árvore em ordem de heap, com -1 para uma posição vazia
- Retornainteger-array
- o valor visto de cima em cada coluna, começando pela coluna mais à esquerda
Restrições
1 ≤ root.length ≤ 32767- Cada
root[i]é-1ou um valor com0 ≤ root[i] ≤ 104, e nenhum valor aparece duas vezes. root[0]nunca é-1, então a árvore tem pelo menos um nó.- O array pode terminar com entradas
-1extras após o último nó. - Ambos os filhos de um espaço vazio também estão vazios, e a árvore tem no máximo
14níveis.
Exemplos
- Entrada
- root = [5, 2, 9, 4, 7, 1, 6]
- Saída
- [4, 2, 5, 9, 6]
- Explicação
- Da esquerda para a direita, as colunas contêm
4(coluna -2),2(-1), a raiz5(0),9(1) e6(2).7e1também ficam na coluna 0, mas a raiz está acima deles.
- Entrada
- root = [1, 2, -1, -1, 3, -1, -1, -1, -1, -1, 4]
- Saída
- [2, 1, 4]
- Explicação
2está na coluna -1. Seu filho à direita3, no índice 4, volta à coluna 0, abaixo da raiz, então fica oculto.4, no índice 10, filho à direita de3, está sozinho na coluna 1.
- Entrada
- root = [6, -1, -1]
- Saída
- [6]
- Explicação
- Os índices 1 e 2 contêm
-1, então6fica sozinho na coluna 0 e é toda a visualização.
Para ir além
Com objetos de nó em vez de um array, não há índice para desempatar. Como você enunciaria a regra de desempate, e o seu código de busca em largura mudaria?
Dicas
Abra uma de cada vez. Cada uma revela um pouco mais.
Numere as colunas: a raiz é 0, um passo à esquerda subtrai 1 e um passo à direita soma 1. Qual nó de uma coluna você consegue ver de cima, e como diferenciá-lo dos outros nós dessa coluna?
Se você visitar os nós nível por nível e, da esquerda para a direita dentro de cada nível, qual nó de uma coluna você encontra primeiro?
Execute uma busca em largura pelos pares (índice, coluna), adicionando o filho esquerdo antes do direito. Armazene um valor somente na primeira vez que uma coluna aparecer, acompanhe a menor e a maior coluna e leia as colunas em ordem no final.
Solução
Cada coluna mostra seu nó mais raso e, em caso de empate, aquele que aparece primeiro na ordem por nível. Essa é exatamente a ordem em que uma busca em largura visita os nós, então o primeiro nó que a busca encontra em uma coluna é a resposta. Uma caminhada em profundidade também funciona, mas precisa acompanhar as profundidades e desempatar com cuidado.
Busca em profundidade, nó mais superficial por coluna
Intuição
Percorra a árvore em profundidade com a coluna e a profundidade de cada nó e mantenha, para cada coluna, a profundidade e o valor do nó mais raso encontrado até então.
Substitua somente quando a profundidade for estritamente menor. Entre dois nós empatados, o que aparece primeiro na ordem por níveis é alcançado primeiro por uma travessia que explora cada subárvore esquerda antes da direita correspondente, pois, quando os dois caminhos divergem, ela segue para a esquerda. Substituir quando as profundidades são iguais faz com que o nó posterior prevaleça: com o índice 3 vazio e nós nos índices 19 e 23, ambos ficam na coluna -2, na profundidade 4, e a resposta é o nó no índice 19.
As colunas ocupadas formam um intervalo contínuo, pois um nó na coluna -3 tem ancestrais nas colunas -1 e -2; portanto, acompanhe a menor e a maior coluna. Cada nó é visitado uma vez, em tempo O(n), e a recursão tem cerca de h chamadas de profundidade.
Algoritmo
- Visite a raiz com a coluna 0 e a profundidade 0. Retorne imediatamente se o índice estiver fora do array ou contiver
-1. - Se a coluna ainda não tiver um nó, ou se esta profundidade for estritamente menor que a armazenada, armazene esta profundidade e o valor.
- Visite
2i+1com a colunacol-1e, em seguida,2i+2com a colunacol+1, um nível mais fundo. - Retorne os valores armazenados da menor coluna para a maior.
def topView(root):
n = len(root)
best = {} # column -> (depth, value) of the shallowest node found so far
def visit(i, col, depth):
if i >= n or root[i] == -1:
return
# Replace only on a strictly smaller depth: on a tie, the node
# met first is the one further left, which comes first in level order.
if col not in best or depth < best[col][0]:
best[col] = (depth, root[i])
visit(2 * i + 1, col - 1, depth + 1)
visit(2 * i + 2, col + 1, depth + 1)
visit(0, 0, 0)
# The columns that hold a node form one unbroken range.
return [best[col][1] for col in range(min(best), max(best) + 1)]Busca em largura, primeiro nó por coluna
Intuição
Uma fila que recebe a raiz, depois o filho esquerdo de cada nó e, em seguida, seu filho direito, libera os nós em ordem por nível: todos os nós de profundidade d antes de qualquer nó de profundidade d+1, e cada nível da esquerda para a direita. Essa é a prioridade do problema, então o primeiro nó recebido por uma coluna é o que se vê de cima, e todos os posteriores são ignorados.
Cada nó custa uma consulta ao mapa, O(n) no total. A fila pode conter um nível inteiro, até cerca de metade dos nós, então o espaço é O(n).
Algoritmo
- Coloque o par (índice 0, coluna 0) em uma fila.
- Retire um par (
i,col). Se o mapa não tiver uma entrada paracol, armazeneroot[i]ali. - Adicione (
2i+1,col-1) e, em seguida, (2i+2,col+1), para cada filho existente. - Quando a fila estiver vazia, retorne os valores do mapa da menor coluna para a maior.
from collections import deque
def topView(root):
n = len(root)
top = {} # column -> value of the first node seen in that column
queue = deque([(0, 0)]) # (node index, column); the root is never empty
while queue:
i, col = queue.popleft()
# The queue hands out shallower nodes first, and left before right
# on one level, so the first node of a column is the one seen from above.
if col not in top:
top[col] = root[i]
left, right = 2 * i + 1, 2 * i + 2
if left < n and root[left] != -1:
queue.append((left, col - 1))
if right < n and root[right] != -1:
queue.append((right, col + 1))
# The columns that hold a node form one unbroken range.
return [top[col] for col in range(min(top), max(top) + 1)]
Armadilhas e casos extremos
- Manter o primeiro nó que uma busca em profundidade encontra. Se o filho esquerdo da raiz tiver um filho direito que, por sua vez, tiver um filho direito (coluna 1, profundidade 3), a busca o encontra antes do filho direito da raiz (coluna 1, profundidade 1).
- Enfileirar primeiro o filho direito: os empates são resolvidos a favor do nó errado.
- Ordenar as colunas como texto:
"-1"vem antes de"-2". - Supor que a subárvore esquerda preenche as colunas negativas. No exemplo 2, o nó
4está na subárvore esquerda da raiz e na coluna 1.
Perguntas frequentes3
O que é a vista superior de uma árvore binária?
Numere as colunas a partir da raiz, começando em 0, uma unidade à esquerda para um filho à esquerda e uma unidade à direita para um filho à direita. A vista superior lista, para cada coluna da esquerda para a direita, o nó mais próximo da raiz.
Qual é a complexidade de tempo da vista superior de uma árvore binária?
Uma busca em largura visita cada um dos n nós uma vez, com uma consulta ao mapa em O(1) por nó; portanto, ela é executada em tempo O(n) se você acompanhar a menor e a maior coluna. Um mapa ordenado de colunas torna a complexidade O(n log w) para w colunas.
Qual é a diferença entre a vista superior, a vista lateral esquerda e a vista inferior?
A vista esquerda seleciona o primeiro nó de cada nível. A vista superior seleciona o nó mais raso de cada coluna. A vista inferior seleciona o nó mais profundo por coluna, então uma busca em largura para encontrá-lo sobrescreve cada coluna em vez de manter o primeiro nó.
Problemas parecidos
Problemas que usam as mesmas ideias. Resolver dois ou três é o que fixa um padrão.
def topView(root):
# Escreva o código aquiTeste #1
Teste #2
Teste #3
Argumento
root = [5, 2, 9, 4, 7, 1, 6]
Saída Esperada
[4, 2, 5, 9, 6]