Subtree of Another Tree
Você recebe duas árvores binárias, root e subRoot, cada uma armazenada em um array em ordem por nível: a raiz no índice 0, os filhos do índice i em 2*i+1 (esquerdo) e 2*i+2 (direito), e -1 para uma posição vazia. Um array pode terminar com entradas extras -1.
Retorne true se algum nó de root, com todos os seus descendentes, tiver exatamente a mesma forma e os mesmos valores que subRoot, e false caso contrário. A árvore inteira root também conta.
Função
- rootinteger-array
- a árvore a ser pesquisada, em ordem por nível, com -1 para uma posição vazia
- subRootinteger-array
- a árvore a ser procurada, no mesmo formato
- Retornaboolean
- true se subRoot aparece em root como um nó com todos os seus descendentes, false caso contrário
Restrições
1 ≤ root.length ≤ 32767e1 ≤ subRoot.length ≤ 32767- Cada entrada é
-1ou um valor de0a104. root[0]esubRoot[0]nunca são-1, então ambas as árvores têm pelo menos um nó.- Qualquer um dos arrays pode terminar com entradas
-1extras após seu último nó. - Ambos os filhos de um espaço vazio também estão vazios, e cada árvore tem profundidade de no máximo
14. - Os valores podem se repetir.
Exemplos
- Entrada
- root = [6, 2, 8, 1, 3, -1, 9]subRoot = [2, 1, 3]
- Saída
- true
- Explicação
- O nó
2no índice1tem os filhos1e3, e ambos são folhas. Isso é exatamentesubRoot.
- Entrada
- root = [6, 2, 8, 1, 3, -1, 9, -1, -1, 0]subRoot = [2, 1, 3]
- Saída
- false
- Explicação
- O
3abaixo do nó2agora tem um filho0. Uma subárvore inclui todos os descendentes, então o nó2contém quatro nós, contra os três desubRoot, e nenhum outro nó contém um2.
- Entrada
- root = [5, 5, 7]subRoot = [5]
- Saída
- true
- Explicação
- A raiz
5tem dois filhos, então não corresponde ao nó único5. O5no índice1é uma folha e corresponde.
Para ir além
Você consegue criar uma impressão digital de cada subárvore a partir de seu valor e das impressões digitais de seus filhos, de modo que uma comparação com subRoot custe O(1)? O que você faz quando duas subárvores diferentes colidem?
Dicas
Abra uma de cada vez. Cada uma revela um pouco mais.
Se
subRootaparecer dentro deroot, seu topo fica sobre um nó deroot. Quais nós poderiam ser esse, e o que você verifica neles?Escreva uma função que diga se duas árvores são idênticas: ambas vazias ou com o mesmo valor no topo, subárvores esquerdas idênticas e subárvores direitas idênticas. Um espaço vazio de um lado e um nó real do outro significa que elas são diferentes.
Execute essa verificação a partir de cada nó de
root. Para pular a maioria deles, calcule primeiro a altura de cada nó: somente um nó cuja altura seja igual à altura desubRootpode corresponder.
Solução
O problema tem duas partes: decidir se duas árvores são idênticas e escolher onde em root executar essa verificação. Apenas um nó cuja altura seja igual à altura de subRoot pode corresponder, e nós de uma mesma altura nunca se sobrepõem, então verificar apenas esses nós percorre cada nó de root no máximo uma vez.
Compare em cada nó
Intuição
Uma posição i é válida quando i está dentro do array e o valor ali não é -1. Duas árvores são idênticas quando ambas estão vazias ou quando seus nós do topo contêm o mesmo valor e suas subárvores esquerda e direita são idênticas. same(i, j) percorre root a partir de i e subRoot a partir de j passo a passo. Se subRoot aparece em root, seu nó do topo está em algum nó; então, tente same(i, 0) a partir de cada i válido.
Uma correspondência quase exata pode levar até m passos antes de falhar, para cada um dos n nós: O(n·m). Aqui, o limite de profundidade restringe isso: um nó só é percorrido pelas verificações que começam nele ou acima dele, no máximo 14.
Algoritmo
- Escreva
same(i, j). Se a posiçãoiemroote a posiçãojemsubRootestiverem ambas vazias, retornetrue; se exatamente uma estiver vazia, retornefalse. - Se os dois valores forem diferentes, retorne
false. Caso contrário, retornesame(2*i+1, 2*j+1)esame(2*i+2, 2*j+2). - Retorne
truesesame(i, 0)for verdadeiro para algum índice válidoideroot, efalsecaso contrário.
def isSubtree(root, subRoot):
n, m = len(root), len(subRoot)
def same(i, j):
# True if the subtree at root[i] equals the subtree at subRoot[j].
a = i < n and root[i] != -1
b = j < m and subRoot[j] != -1
if not a or not b:
return a == b # two empty spots match, one empty spot does not
if root[i] != subRoot[j]:
return False
return same(2 * i + 1, 2 * j + 1) and same(2 * i + 2, 2 * j + 2)
# Try every real node of root as the top of a match.
for i in range(n):
if root[i] != -1 and same(i, 0):
return True
return FalseCompare apenas onde as alturas correspondem
Intuição
Árvores idênticas têm a mesma altura, contada como o número de nós no caminho mais longo para baixo. Portanto, só vale a pena verificar os nós de root cuja altura é igual à altura de subRoot. No primeiro exemplo, subRoot tem altura 2, e, em root, apenas os nós 2 e 8 têm essa altura.
Um nó é mais alto do que todos os nós abaixo dele, então dois nós de mesma altura nunca ficam um dentro do outro. Cada verificação percorre apenas a subárvore do nó inicial, portanto, juntas, as verificações percorrem cada nó de root no máximo uma vez.
Os filhos ficam em índices maiores do que o pai, então uma varredura do último índice para o primeiro alcança os dois filhos antes do pai. As duas varreduras de altura e as verificações levam O(n + m) de tempo.
Algoritmo
- Preencha um array de alturas para cada árvore, percorrendo os índices do último até
0: uma posição vazia recebe0, um nó real recebe1mais a maior altura entre seus dois filhos. - Para cada índice
iderootcuja altura seja igual à altura no índice0desubRoot, executesame(i, 0)e retornetruese a condição for satisfeita. - Retorne
false.
def isSubtree(root, subRoot):
n, m = len(root), len(subRoot)
def same(i, j):
# True if the subtree at root[i] equals the subtree at subRoot[j].
a = i < n and root[i] != -1
b = j < m and subRoot[j] != -1
if not a or not b:
return a == b # two empty spots match, one empty spot does not
if root[i] != subRoot[j]:
return False
return same(2 * i + 1, 2 * j + 1) and same(2 * i + 2, 2 * j + 2)
def heights(tree):
# Children sit at larger indexes, so a backward scan finishes them first.
h = [0] * len(tree)
for i in range(len(tree) - 1, -1, -1):
if tree[i] != -1:
left = h[2 * i + 1] if 2 * i + 1 < len(tree) else 0
right = h[2 * i + 2] if 2 * i + 2 < len(tree) else 0
h[i] = 1 + max(left, right)
return h
target = heights(subRoot)[0]
rootHeights = heights(root)
# Only a node of the same height can match, and no two such nodes overlap.
for i in range(n):
if rootHeights[i] == target and same(i, 0):
return True
return False
Armadilhas e casos extremos
- Aceitar uma correspondência parcial, como no segundo exemplo.
samedeve falhar quandoroottiver um nó em uma posição ondesubRoottiver uma posição vazia. - Parar no primeiro nó com o valor correto. No terceiro exemplo, a raiz
5falha e a folha5abaixo dela corresponde. - Comparar fatias dos dois arrays. Os nós de uma subárvore se distribuem por vários níveis, e qualquer um dos arrays pode terminar com entradas
-1extras. Um índice além do fim é uma posição vazia, como um-1. - Em Lua e R, mantenha os índices dos nós começando em 0 para a operação aritmética
2*i+1e leiaroot[i + 1].
Perguntas frequentes3
Qual é a complexidade de tempo de Subtree of Another Tree?
Comparar a partir de cada nó custa O(n·m) no pior caso, em que n e m são os tamanhos das duas árvores. Comparar apenas nos nós cuja altura é igual à altura de subRoot reduz o custo para O(n + m), porque cada nó de root é percorrido no máximo uma vez.
É possível resolver Subtree of Another Tree usando correspondência de strings?
Sim. Escreva cada árvore em pré-ordem, usando # para cada filho vazio e um separador antes de cada valor: ,2,1,#,#,3,#,#. subRoot é uma subárvore exatamente quando sua string aparece dentro da string de root, e o KMP a encontra em O(n + m). Sem os marcadores, formatos diferentes geram a mesma string; sem separadores, 12 contém 2.
Um nó que corresponde a subRoot, mas tem filhos extras, conta?
Não. Uma subárvore é um nó junto com todos os seus descendentes, então todos os nós abaixo do que corresponde também devem aparecer em subRoot. No segundo exemplo, o 0 extra abaixo do 3 faz com que a resposta seja false.
Problemas parecidos
Problemas que usam as mesmas ideias. Resolver dois ou três é o que fixa um padrão.
def isSubtree(root, subRoot):
# Escreva o código aquiTeste #1
Teste #2
Teste #3
Argumentos
root = [6, 2, 8, 1, 3, -1, 9] subRoot = [2, 1, 3]
Saída Esperada
true