Três Problemas Clássicos com Árvores Binárias: Diferença Mínima em BST, Moda em Árvore de Busca e Ancestral Comum

Este artigo aborda três desafios fundamentais envolvendo estruturas de árvores binárias, com foco em otimizações específicas para árvores de busca binária (BST) e estratégias recursivas robustas para árvores genéricas.

Diferença Absoluta Mínima entre Nós em uma BST

Dado o nó raiz de uma árvore de busca binária, calcule a menor diferença absoluta entre os valores de quaisquer dois nós distintos. A propriedade fundamental da BST — onde um percurso em ordem produz uma sequência ordenada — permite resolver esse problema em tempo linear sem necessidade de armazenamento adicional.

A abordagem consiste em realizar uma travessia em ordem (in-order), mantendo referência ao valor do nó anterior visitado. Em cada passo, calcula-se a diferença entre o valor atual e o anterior, atualizando o mínimo global sempre que uma diferença menor for encontrada.

class Solution {
    private Integer valorAnterior = null;
    private int diferencaMinima = Integer.MAX_VALUE;

    public int minDiffInBST(TreeNode raiz) {
        percorrerEmOrdem(raiz);
        return diferencaMinima;
    }

    private void percorrerEmOrdem(TreeNode no) {
        if (no == null) return;

        percorrerEmOrdem(no.left);

        if (valorAnterior != null) {
            int diff = no.val - valorAnterior;
            if (diff < diferencaMinima) {
                diferencaMinima = diff;
            }
        }
        valorAnterior = no.val;

        percorrerEmOrdem(no.right);
    }
}

Identificação de Todos os Valores Modais em uma BST

Dada uma BST que pode conter valores duplicados, retorne todos os elementos que ocorrem com a maior frequência. Ao contrário de soluções baseadas em hashmaps, aproveita-se a ordenação implícita da travessia em ordem para detectar agrupamentos contíguos de valores iguais, reduzindo o uso de memória e evitando sobrecarga de estruturas auxiliares.

O algoritmo rastreia dinamicamente a contagem corrente do valor atual, compara-a com a contagem máxima observada até então, e atualiza a lista de modas conforme necessário — limpando-a ao encontrar uma nova frequência superior ou adicionando ao conjunto ao igualar a frequência máxima.

class Solution {
    private Integer valorPrecedente = null;
    private int contagemAtual = 0;
    private int frequenciaMaxima = 0;
    private final List<Integer> resultados = new ArrayList<>();

    public int[] findMode(TreeNode raiz) {
        explorarEmOrdem(raiz);
        return resultados.stream().mapToInt(Integer::intValue).toArray();
    }

    private void explorarEmOrdem(TreeNode no) {
        if (no == null) return;

        explorarEmOrdem(no.left);

        if (valorPrecedente != null && no.val == valorPrecedente) {
            contagemAtual++;
        } else {
            contagemAtual = 1;
        }

        if (contagemAtual > frequenciaMaxima) {
            frequenciaMaxima = contagemAtual;
            resultados.clear();
            resultados.add(no.val);
        } else if (contagemAtual == frequenciaMaxima) {
            resultados.add(no.val);
        }

        valorPrecedente = no.val;

        explorarEmOrdem(no.right);
    }
}

Localização do Ancestral Comum Mais Profundo

Para duas referências arbitrárias de nós em uma árvore binária não ordenada, determine o nó mais profundo que seja ancestral de ambos. A solução emprega uma estratégia recursiva pós-ordem: cada chamada retorna null se nenhum dos nós-alvo for encontrado na subárvore, ou retorna a referência do nó correspondente assim que localizado. O ancestral comum é identificado quando ambas as chamadas recursivas (esquerda e direita) retornam valores não nulos — indicando que os dois nós estão em subárvores distintas do nó atual.

Caso um dos nós seja exatamente o nó atual, e o outro esteja em alguma subárvore descendente, o nó atual também é o ancestral comum — condição capturada pela verificação inicial na assinatura recursiva.

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode raiz, TreeNode alvoA, TreeNode alvoB) {
        if (raiz == null || raiz == alvoA || raiz == alvoB) {
            return raiz;
        }

        TreeNode esquerda = lowestCommonAncestor(raiz.left, alvoA, alvoB);
        TreeNode direita = lowestCommonAncestor(raiz.right, alvoA, alvoB);

        if (esquerda != null && direita != null) {
            return raiz;
        }
        return (esquerda != null) ? esquerda : direita;
    }
}

Tags: binary-search-tree tree-traversal recursion Algorithm Binary-Tree

Publicado em 8-30 18:53