Manipulação e Reconstrução de Árvores Binárias: Algoritmos e Implementações

Localizando o Valor na Última Linha à Esquerda

Para encontrar o valor mais à esquerda na última linha de uma árvore binária, podemos utilizar uma busca em profundidade (DFS) que prioriza a exploração do lado esquerdo e rastreia a profundidade máxima alcançada.

class Solution {
public:
    int profundidadeAlvo = -1;
    int valorFinal;

    void buscarEsquerda(TreeNode* no, int nivel) {
        if (!no->left && !no->right) {
            if (nivel > profundidadeAlvo) {
                profundidadeAlvo = nivel;
                valorFinal = no->val;
            }
            return;
        }

        if (no->left) buscarEsquerda(no->left, nivel + 1);
        if (no->right) buscarEsquerda(no->right, nivel + 1);
    }

    int findBottomLeftValue(TreeNode* root) {
        buscarEsquerda(root, 0);
        return valorFinal;
    }
};

Verificação de Soma de Caminho

Este algoritmo verifica se existe um caminho da raiz até uma folha onde a soma dos valores dos nós é igual a um valor alvo. A abordagem mais eficiente utiliza a subtração do valor atual do total restante.

class Solution {
public:
    bool hasPathSum(TreeNode* node, int somaRestante) {
        if (!node) return false;
        
        // Verifica se é um nó folha
        if (!node->left && !node->right) {
            return somaRestante == node->val;
        }
        
        int novoAlvo = somaRestante - node->val;
        return hasPathSum(node->left, novoAlvo) || hasPathSum(node->right, novoAlvo);
    }
};

Recuperando Todos os Caminhos com Soma Específica

Diferente da verificação simples, aqui precisamos retornar todos os caminhos completos. Utilizamos backtracking para adicinoar e remover elementos de uma lista temporária conforme percorremos a árvore.

class Solution {
public:
    vector<vector>> todasAsRotas;
    
    void encontrarCaminhos(TreeNode* node, int alvo, vector<int>& atual) {
        if (!node) return;
        
        atual.push_back(node->val);
        
        if (!node->left && !node->right && alvo == node->val) {
            todasAsRotas.push_back(atual);
        } else {
            encontrarCaminhos(node->left, alvo - node->val, atual);
            encontrarCaminhos(node->right, alvo - node->val, atual);
        }
        
        atual.pop_back(); // Backtracking
    }

    vector<vector>> pathSum(TreeNode* root, int targetSum) {
        vector<int> caminhoTemporario;
        encontrarCaminhos(root, targetSum, caminhoTemporario);
        return todasAsRotas;
    }
};</int></vector></int></vector>

Construção de Árvore a partir de Travessias Inorder e Postorder

A reconstrução baseia-se no fato de que o último elemento da sequência postorder é sempre a raiz. Ao localizar essa raiz na sequência inorder, dividimos a árvore em subárvores esquerda e direita.

class Solution {
public:
    TreeNode* montarArvore(vector<int>& inorder, vector<int>& postorder) {
        if (postorder.empty()) return nullptr;

        int valorRaiz = postorder.back();
        TreeNode* raiz = new TreeNode(valorRaiz);

        if (postorder.size() == 1) return raiz;

        // Localiza divisor no Inorder
        int divisor = 0;
        for (; divisor < inorder.size(); divisor++) {
            if (inorder[divisor] == valorRaiz) break;
        }

        // Segmentação dos vetores
        vector<int> inEsq(inorder.begin(), inorder.begin() + divisor);
        vector<int> inDir(inorder.begin() + divisor + 1, inorder.end());

        postorder.pop_back();
        vector<int> postEsq(postorder.begin(), postorder.begin() + inEsq.size());
        vector<int> postDir(postorder.begin() + inEsq.size(), postorder.end());

        raiz->left = montarArvore(inEsq, postEsq);
        raiz->right = montarArvore(inDir, postDir);

        return raiz;
    }
};</int></int></int></int></int></int>

Construção de Árvore a partir de Travessias Preorder e Inorder

Similar ao método anterior, mas aqui a raiz é identificada no início da sequência preorder.

class Solution {
public:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        if (preorder.empty()) return nullptr;

        int topo = preorder[0];
        TreeNode* root = new TreeNode(topo);

        if (preorder.size() == 1) return root;

        int mid = 0;
        for (; mid < inorder.size(); mid++) {
            if (inorder[mid] == topo) break;
        }

        vector<int> inEsq(inorder.begin(), inorder.begin() + mid);
        vector<int> inDir(inorder.begin() + mid + 1, inorder.end());

        vector<int> preResto(preorder.begin() + 1, preorder.end());
        vector<int> preEsq(preResto.begin(), preResto.begin() + inEsq.size());
        vector<int> preDir(preResto.begin() + inEsq.size(), preResto.end());

        root->left = buildTree(preEsq, inEsq);
        root->right = buildTree(preDir, inDir);

        return root;
    }
};</int></int></int></int></int></int></int>

Tags: Binary-Tree C++ backtracking dfs algorithms

Publicado em 7-20 17:46