devly
Voltar para Algoritmos

A Régua da Eficiência: Dominando o Big O

Entenda como medir a eficiência de algoritmos, por que isso é crucial em entrevistas e como analisar a complexidade de tempo e espaço do seu código.

Atualizado em: 8 de Junho, 2025
Notas do Autor

E aí, Dev! Vou te contar um segredo: no início da minha carreira, eu achava que "código bom" era código que funcionava. Se eu apertasse "run" e o resultado saísse na tela, missão cumprida. Até que, em uma das minhas primeiras entrevistas para uma Big Tech, o entrevistador olhou meu código e perguntou: "Ok, legal. E qual é o Big O disso?".

Eu gelei. Fiquei em silêncio por uns segundos que pareceram horas. Eu não sabia responder. Naquele dia, aprendi da forma mais difícil que, no mercado global, "funcionar" é o mínimo. Eles querem saber se o seu código escala.

Big O não é um bicho de sete cabeças; é a "régua" que usamos para medir a eficiência do nosso trabalho. É o idioma universal para discutir performance. Dominar isso não é só sobre passar na entrevista, é sobre se tornar um(a) engenheiro(a) de software de alto nível. Bora aprender a usar essa régua?


1. Por que "Funciona" Não é o Bastante? A Escalabilidade

Imagine que você criou um algoritmo para processar uma lista de 10 usuários. Ele roda em 1 milissegundo. Incrível! Mas e se a empresa crescer e essa lista tiver 10 milhões de usuários? Seu algoritmo ainda seria rápido ou levaria horas, travando todo o sistema?

É aqui que entra a escalabilidade: a capacidade do seu código de lidar com um aumento na carga de trabalho ou no volume de dados. A Notação Big O é a forma como descrevemos o quão bem (ou mal) nosso algoritmo escala.


2. O que é (de verdade) a Notação Big O?

Big O não mede o tempo em segundos ou minutos. Ele descreve a taxa de crescimento do tempo de execução (ou do uso de memória) de um algoritmo em relação ao tamanho da entrada (n).

Em outras palavras, ele responde à pergunta: "Se o número de itens na minha entrada dobrar, o que acontece com o tempo de execução? Ele também dobra? Quadruplica? Ou mal se altera?". O Big O foca sempre no pior cenário (worst-case scenario), pois é o que nos dá uma garantia de performance.

Tempo vs. Espaço 🕰️💾

Existem dois tipos principais de complexidade:
  • Complexidade de Tempo (Time Complexity): O que mais importa em 95% das entrevistas. Mede quantas operações o algoritmo executa.
  • Complexidade de Espaço (Space Complexity): Mede quanta memória adicional o algoritmo precisa para rodar. Também importante, especialmente com grandes volumes de dados.

3. As "Etiquetas" de Eficiência: Complexidades Comuns

Vamos conhecer as "etiquetas" mais comuns que você vai usar para classificar seus algoritmos, da melhor para a pior.

O(1) - Tempo Constante 🏆

O que é: O tempo de execução não depende do tamanho da entrada (n). É o Santo Graal da eficiência.

Exemplo: Acessar um elemento em um array pelo índice.

javascript
function getFirstElement(arr) {
  return arr[0]; // Sempre 1 operação, não importa o tamanho do array
}

O(log n) - Tempo Logarítmico 🚀

O que é: O tempo de execução cresce de forma logarítmica. Geralmente, ocorre em algoritmos que "quebram" o problema pela metade a cada passo. É extremamente eficiente.

Exemplo: Busca Binária (Binary Search) em um array ordenado.

javascript
function binarySearch(arr, target) {
  let left = 0, right = arr.length - 1;
  while (left <= right) {
    let mid = Math.floor((left + right) / 2);
    if (arr[mid] === target) return mid;
    if (arr[mid] < target) left = mid + 1;
    else right = mid - 1;
  }
  return -1; // A cada passo, o espaço de busca é dividido por 2
}

O(n) - Tempo Linear 🏃

O que é: O tempo de execução cresce na mesma proporção que o tamanho da entrada. Se a entrada dobra, o tempo dobra. É a complexidade mais comum para uma primeira abordagem ("força bruta").

Exemplo: Encontrar um item em uma lista não ordenada.

javascript
function findValue(arr, value) {
  for (let i = 0; i < arr.length; i++) { // O loop passa por cada item
    if (arr[i] === value) return i;
  }
  return -1;
}

O(n log n) - Tempo Log-Linear 💨

O que é: Um pouco mais lento que O(n), mas ainda muito eficiente. É a complexidade dos melhores algoritmos de ordenação baseados em comparação.

Exemplo: Merge Sort, Quick Sort (no caso médio).

O(n²) - Tempo Quadrático 🐌

O que é: O tempo de execução cresce ao quadrado do tamanho da entrada. Geralmente envolve um loop aninhado dentro de outro, percorrendo a mesma coleção. Fica lento rapidamente.

Exemplo: Encontrar pares duplicados em uma lista usando dois loops.

javascript
function hasDuplicates(arr) {
  for (let i = 0; i < arr.length; i++) {
    for (let j = i + 1; j < arr.length; j++) { // Loop dentro de loop
      if (arr[i] === arr[j]) return true;
    }
  }
  return false;
}

O(2ⁿ) - Tempo Exponencial 🐢

O que é: O tempo de execução dobra a cada novo elemento adicionado à entrada. Fica inviável para entradas mesmo que moderadamente grandes. Frequentemente visto em soluções recursivas que não usam memoização.

Exemplo: Cálculo de Fibonacci recursivo (a versão ingênua).


4. As Regras do Jogo: Como Calcular o Big O

Calcular o Big O pode parecer complicado, mas segue algumas regras simples. A mais importante é a primeira: sempre analisamos o pior cenário possível.

Regra 1: Foco no Pior Caso (Worst-Case)

O Big O descreve a performance no pior cenário possível. Se um algoritmo pode ser muito rápido na maioria das vezes (melhor caso ou caso médio), mas lento em uma situação específica, é essa situação lenta que define sua complexidade. Isso nos dá uma garantia de performance.

javascript
function findItem(array, item) {
  for (let i = 0; i < array.length; i++) {
    if (array[i] === item) {
      return "Encontrado!"; // Sai mais cedo
    }
  }
  return "Não encontrado.";
}
// Melhor caso: O item é o primeiro. Complexidade O(1).
// Pior caso: O item é o último ou não existe. Complexidade O(n).
// Nossa classificação Big O é sempre o pior caso: O(n).

Regra 2: Ignore as Constantes

O(2n) vira O(n). O que importa é a taxa de crescimento, não o valor exato. 2n cresce na mesma taxa que n (linearmente).

javascript
function printItems(arr) {
  for (const item of arr) {
    console.log(item); // O(n)
  }
  for (const item of arr) {
    console.log(item); // O(n)
  }
}
// Total: O(n + n) = O(2n) -> Simplifica para O(n)

Regra 3: Ignore os Termos Não-Dominantes

O(n² + n) vira O(n²). Quando 'n' fica muito grande, a parte 'n²' domina completamente o crescimento, tornando o '+ n' irrelevante para a análise da escala.

javascript
function printItemsAndPairs(arr) {
  for (const item of arr) { // O(n)
    console.log(item);
  }
  for (let i = 0; i < arr.length; i++) { // O(n²)
    for (let j = 0; j < arr.length; j++) {
      console.log(arr[i], arr[j]);
    }
  }
}
// Total: O(n + n²) -> Simplifica para O(n²)

Regra 4: Some Complexidades para Passos Sequenciais (Inputs Diferentes)

Se você tem um loop O(n) seguido por um loop O(m) para inputs diferentes, a complexidade total é O(n + m). Não podemos simplificar porque não sabemos a relação entre n e m.

javascript
function processTwoArrays(arr1, arr2) {
  for (const item of arr1) { // O(n)
    console.log(item);
  }
  for (const item of arr2) { // O(m)
    console.log(item);
  }
}
// Total: O(n + m)

Regra 5: Multiplique Complexidades para Passos Aninhados

Se você tem um loop O(n) com outro loop O(n) dentro dele, a complexidade é O(n * n) = O(n²). Se o loop interno fosse sobre um input 'm', seria O(n * m).

javascript
function createPairs(arr) {
  for (let i = 0; i < arr.length; i++) { // Loop externo: n vezes
    for (let j = 0; j < arr.length; j++) { // Loop interno: n vezes
      console.log(arr[i], arr[j]); // Executa n*n vezes
    }
  }
}
// Total: O(n * n) = O(n²)

5. Falando a Língua do Entrevistador

Em uma entrevista técnica, o entrevistador não quer só a resposta correta, ele quer entender seu processo de pensamento. Falar sobre Big O é a melhor forma de fazer isso.

Como Usar o Big O para Brilhar ✨

  1. Comece com a Força Bruta: Apresente a solução mais óbvia primeiro, mesmo que seja ineficiente. E, crucialmente, diga sua complexidade. "Minha primeira ideia é usar dois loops para encontrar o par, o que teria uma complexidade de tempo de O(n²)".
  2. Identifique o Gargalo: Mostre que você sabe onde está a ineficiência. "O gargalo aqui são os loops aninhados".
  3. Proponha a Otimização: Sugira a abordagem otimizada, explicando a estrutura de dados ou técnica que você vai usar. "Mas, se eu usar um Hash Map (ou Set), posso percorrer a lista uma vez e verificar se o complemento já existe no mapa. Isso melhora a complexidade."
  4. Declare a Complexidade Otimizada: "Essa nova abordagem teria uma complexidade de tempo de O(n), com um custo de espaço de O(n) para o Hash Map". Isso mostra que você entende os trade-offs!

💊 Pílula Devly

Big O não é sobre decorar nomes, é sobre ter uma conversa profissional sobre eficiência. É a diferença entre dizer 'meu código é rápido' e provar que 'meu código escala de forma inteligente'. Domine essa 'régua' e você estará falando a língua das grandes ligas.

Anterior

Módulo: Desafios de Código e Algoritmos

Voltar

Próximo

Os Blocos Fundamentais: Arrays, Strings e Hash Tables

Continuar