Pular para o conteudo principal

Parênteses válidos

Como perceber que o fechamento precisa casar com a abertura mais recente e por que isso pede pilha.

Dada uma string s contendo apenas os caracteres ()[]{}, retorne true se a string for valida.

Uma string e valida quando:

  • cada abertura e fechada pelo mesmo tipo de parenteses
  • cada abertura e fechada na ordem correta

Exemplo:

Input:  s = "()[]{}"
Output: true

O que perceber antes de codar

Erros comuns

Passo 1 — Entender o que realmente precisa ser validado

Muita gente começa olhando só contagem.

Mas esse problema não e sobre quantidade. E sobre ordem e contexto.

([)] prova isso bem:

  • abriu (
  • abriu [
  • fechou ) antes de fechar [

A contagem bate. A estrutura não.

Passo 2 — Escrever uma versão inicial correta

Uma versão inicial possível e remover pares validos adjacentes até não dar mais:

export function isValid(s: string): boolean {
  let current = s
  let previous = ''

  while (current.length !== previous.length) {
    previous = current
    current = current.replace('()', '').replace('[]', '').replace('{}', '')
  }

  return current.length === 0
}

Funciona, mas e ineficiente. Cada passada cria novas strings e repete trabalho.

Passo 3 — Reformular a pergunta

Quando chega um fechamento, o que você realmente precisa saber?

Não e "existe alguma abertura desse tipo?".

E:

a ultima abertura que ficou pendente combina com este fechamento?

Isso já aponta para uma estrutura LIFO.

Passo 4 — Guardar o contexto aberto

Pilha faz exatamente isso:

  • quando abre, empilha
  • quando fecha, desempilha
  • se o tipo não bater, falhou
  • no final, a pilha precisa estar vazia

O editor interativo precisa de JavaScript. Voce ainda pode ler o desafio e copiar o codigo inicial abaixo.

Codigo inicial

export function isValid(s: string): boolean {
  // sua solução aqui
  return false
}
solution.ts
Travado? Dicas disponiveis.

Ainda não resolveu?

Ver a solução agora pode reduzir o aprendizado. Vale a pena tentar mais um pouco.

Abrir a solucao de referencia

Sem JavaScript, a solucao de referencia aparece inline em vez de um dialogo.

Solução

Complexidade final

Tempo: O(n)

Espaço: O(n)

Solução 1 — Remover pares O(n²)

Uma versão inicial simples e ir removendo pares validos visíveis.

export function isValid(s: string): boolean {
  let current = s
  let previous = ''

  while (current.length !== previous.length) {
    previous = current
    current = current.replace('()', '').replace('[]', '').replace('{}', '')
  }

  return current.length === 0
}

Funciona. Mas fica ruim porque você reprocessa a string várias vezes.

Solução 2 — Pilha O(n)

Se o fechamento precisa casar com a abertura mais recente, use pilha.

export function isValid(s: string): boolean {
  const pairs: Record<string, string> = {
    ')': '(',
    ']': '[',
    '}': '{',
  }

  const stack: string[] = []

  for (const char of s) {
    if (char in pairs) {
      if (stack.pop() !== pairs[char]) {
        return false
      }

      continue
    }

    stack.push(char)
  }

  return stack.length === 0
}

Agora a estrutura do problema bate com a estrutura de dados.

O que dizer na entrevista

Uma explicação curta e boa seria:

O problema não e só de contagem. O fechamento precisa combinar com a abertura mais recente ainda aberta. Por isso pilha encaixa bem: empilho aberturas, desempilho ao fechar, e valido o tipo em O(n).

Isso mostra que você:

  • identificou a regra real do problema
  • justificou a estrutura de dados
  • explicou a solução em termos de contexto, não de truque

Quando esse padrão aparece de novo

Esse padrão reaparece quando você precisa:

  • rastrear contexto aberto
  • validar ordem de fechamento
  • desfazer o ultimo estado primeiro
  • processar estruturas aninhadas

O ponto não e decorar pilha.

O ponto e perceber quando o problema pede “o ultimo que entrou e o primeiro que precisa sair”.

Proximo desafio Melhor hora para comprar e vender ação Desafio anterior Palíndromo válido

Desafios relacionados

Artigos relacionados