Dominando Pilhas e Filas: Implementações Cruzadas e Aplicações Práticas em Algoritmos

Implementação de Fila com Pilhas (LeetCode 232)

Para simular o comportamento FIFO (First-In-First-Out) de uma fila utilizando estruturas LIFO (Last-In-First-Out), a abordagem mais eficiente requer o uso de duas pilhas. Uma pilha é dedicada exclusivamente para receber novos elementos (inStack), enquanto a outra é responsável por fornecer os elementos na ordem de saída (outStack).

A lógica central reside na transferência de elementos: sempre que for necessário remover ou visualizar o elemento da frente e a outStack estiver vazia, todos os elementos da inStack são transferidos para a outStack. Isso inverte a ordem dos elementos, alinhando-os perfeitamente com a semântica de uma fila.

#include <stack>

class CustomQueue {
private:
    std::stack<int> inStack;
    std::stack<int> outStack;

    void transferElements() {
        if (outStack.empty()) {
            while (!inStack.empty()) {
                outStack.push(inStack.top());
                inStack.pop();
            }
        }
    }

public:
    CustomQueue() {}
    
    void push(int x) {
        inStack.push(x);
    }
    
    int pop() {
        transferElements();
        int frontElement = outStack.top();
        outStack.pop();
        return frontElement;
    }
    
    int peek() {
        transferElements();
        return outStack.top();
    }
    
    bool empty() {
        return inStack.empty() && outStack.empty();
    }
};

Implementação de Pilha com Filas (LeetCode 225)

Emular o comportamento LIFO de uma pilha utilizando uma única fila FIFO é perfeitamente viável. A estratégia mais direta consiste em manter a ordem de inserção normal durante a operação de push, mas reorganizar os elementos internamente durante a operação de pop.

Ao remover um elemento, o objetivo é acessar o último ittem inserido. Para isso, calculamos o tamanho atual da fila e movemos os primeiros N - 1 elementos para o final da própria fila. O elemento que restar na frente será o último que entrou, caracterizando o comportamento de uma pilha.

#include <queue>

class CustomStack {
private:
    std::queue<int> dataQueue;

public:
    CustomStack() {}
    
    void push(int x) {
        dataQueue.push(x);
    }
    
    int pop() {
        int currentSize = dataQueue.size();
        for (int i = 0; i < currentSize - 1; ++i) {
            dataQueue.push(dataQueue.front());
            dataQueue.pop();
        }
        int topElement = dataQueue.front();
        dataQueue.pop();
        return topElement;
    }
    
    int top() {
        return dataQueue.back();
    }
    
    bool empty() {
        return dataQueue.empty();
    }
};

Validação de Parênteses Ballanceados (LeetCode 20)

A verificação de parênteses balanceados é um caso clássico de aplicação de pilhas. A solução percorre a string caractere por caractere. Ao encontrar um parêntese de abertura, o respectivo parêntese de fechamento correspondente é empilhado. Ao encontrar um caractere de fechamento, verifica-se se a pilha está vazia ou se o topo da pilha não corresponde ao caractere atual.

É crucial realizar uma poda inicial: se o comprimento da string for ímpar, ela nunca poderá ser válida. Além disso, a ordem das verificações condicionais é fundamental; verificar se a pilha está vazia antes de acessar o elemento do topo evita exceções de execução em casos como ) no início da string.

#include <string>
#include <stack>

class ParenthesesValidator {
public:
    bool isValid(const std::string& s) {
        if (s.length() % 2 != 0) return false;
        
        std::stack<char> bracketStack;
        
        for (char c : s) {
            if (c == '(') bracketStack.push(')');
            else if (c == '{') bracketStack.push('}');
            else if (c == '[') bracketStack.push(']');
            else {
                if (bracketStack.empty() || bracketStack.top() != c) {
                    return false;
                }
                bracketStack.pop();
            }
        }
        
        return bracketStack.empty();
    }
};

Remoção de Duplicatas Adjacentes (LeetCode 1047)

Para eliminar caarcteres duplicados e adjacentes em uma string, podemos utilizar a própria string de resultado como uma estrutura de pilha. A ideia é iterar sobre a string original e comparar cada caractere com o último caractere adicionado à string de resultado.

Se houver correspondência, o caractere duplicado é removido (operação de pop). Caso contrário, o novo caractere é anexado (operação de push). Essa abordagem elimina a necessidade de uma pilha separada e evita a etapa de inversão da string no final, resultando em um código mais limpo e performático.

#include <string>

class DuplicateRemover {
public:
    std::string removeDuplicates(const std::string& s) {
        std::string processed;
        
        for (char c : s) {
            if (!processed.empty() && processed.back() == c) {
                processed.pop_back();
            } else {
                processed.push_back(c);
            }
        }
        
        return processed;
    }
};

Tags: Stack queue data-structures LeetCode cplusplus

Publicado em 7-3 09:12