Estruturas de dados e algoritmos: o guia para subir de nível

O que vem depois do básico da programação: matrizes, dicionários e conjuntos, busca e ordenação, a noção de eficiência (Big-O) e código limpo, explicados de forma simples, com a ordem certa de estudo.

Revisado pela equipe editorial ValorFinalDocumentação oficial do Python e MDN Web Docs
Calcule agora: JSON FormatterResultado na hora, de graça e sem cadastro.

Depois de aprender a pensar em sequência, decisão e repetição, vem o degrau que separa quem copia solução de quem cria solução: escolher como organizar os dados e conhecer os algoritmos clássicos. A ideia central é simples e vale ouro. A mesma tarefa, com a estrutura errada, custa um milhão de passos; com a estrutura certa, custa um. Este guia mostra as estruturas fundamentais, o custo real de cada operação em notação Big O e como decidir qual usar, sem matemática pesada e com código Python que roda. Para aprender tudo na prática, com jogos, desenhos, laboratório e certificado, use o Curso de Lógica de Programação Intermediário, gratuito e parte da Trilha Lógica.

Resposta rápida

  • Estrutura de dados é o formato em que você guarda muitos valores: lista, dicionário, conjunto, pilha, fila, árvore, heap e grafo.
  • Big O descreve como o custo cresce com o tamanho da entrada: O(1) constante, O(log n) logarítmico, O(n) linear, O(n log n), O(n²) quadrático e O(2^n) exponencial.
  • Buscar por posição é O(1), mas buscar por valor em lista não ordenada é O(n); no dicionário, buscar por chave é O(1) em média.
  • Escolher a estrutura certa muda a complexidade do problema: a regra de ouro é casar a operação mais frequente com a estrutura que a faz barata.

O que são estruturas de dados e por que a escolha muda tudo

No começo da programação você lida com poucos valores por vez: uma nota, dois preços, um nome. O mundo real vem em montes: a turma inteira, o extrato do mês, o tabuleiro completo. Guardar isso em variáveis soltas não escala, então usamos estruturas de dados, que são recipientes organizados para muitos dados de uma vez. O ponto que separa o programador iniciante do intermediário não é conhecer todas elas, é sentir qual pede menos esforço para o problema. Precisa buscar rápido por um identificador? Um dicionário resolve em O(1). Precisa manter tudo em ordem e processar por chegada? Uma fila. Precisa do maior valor a cada passo? Um heap. A estrutura errada não trava o programa, mas o deixa lento e cheio de gambiarra. A certa faz o código curto e o desempenho previsível.

Big O: como o tempo cresce com o tamanho da entrada

Dois programas podem dar o mesmo resultado e mesmo assim serem muito diferentes: um responde na hora, o outro trava com dados grandes. Para comparar sem depender da máquina, contamos passos em vez de segundos e perguntamos como esse número cresce quando a entrada aumenta. Esse é o papel do Big O. Ele ignora constantes e detalhes pequenos e foca no formato do crescimento, que é o que domina quando os dados ficam grandes. A tabela abaixo mostra as classes que aparecem quase sempre, com o teste mental de dobrar a entrada e um exemplo concreto de cada uma.

NotaçãoNomeSe a entrada dobra, o trabalho...Exemplo típico
O(1)constantenão mudaler lista[i], acessar dict[chave]
O(log n)logarítmicosoma um passobusca binária, altura de árvore balanceada
O(n)lineardobrabusca linear, percorrer a lista uma vez
O(n log n)linearítmicopouco mais que dobramerge sort, quick sort médio, sorted()
O(n²)quadráticoquadruplicadois laços aninhados, bubble sort
O(2^n)exponencialeleva ao quadrado (explode)força bruta de subconjuntos, fibonacci recursivo ingênuo

A leitura prática é esta: O(1) e O(log n) são ótimos e aguentam qualquer volume; O(n) e O(n log n) são o território normal de código bem escrito; O(n²) já pesa em listas grandes e O(2^n) só funciona em entradas minúsculas. Quando você melhora um trecho de O(n) para O(1) trocando uma lista por um dicionário, não ganha 10% de desempenho: ganha uma ordem de grandeza que aparece justamente quando o sistema cresce.

As estruturas fundamentais e o custo de cada operação

Cada estrutura é boa em algumas operações e ruim em outras, e o segredo é conhecer esse perfil. A tabela reúne as complexidades típicas de acesso, busca, inserção e remoção das estruturas que você mais vai usar. Onde a célula traz um asterisco ou um símbolo, a nota da última coluna explica a condição.

EstruturaAcesso por posiçãoBusca por valorInserçãoRemoçãoObservação
Array / lista (Python list)O(1)O(n)O(1)* fim, O(n) meioO(n)*amortizado ao inserir no fim
Lista ligadaO(n)O(n)O(1)O(1)O(1) só se você já tem o nó em mãos
Pilha (stack)O(1) topoO(n)O(1)O(1)LIFO: último a entrar sai primeiro
Fila (deque)O(1) pontasO(n)O(1)O(1)FIFO: primeiro a entrar sai primeiro
Tabela hash / dicionárioO(1)† por chaveO(1)† chave, O(n) valorO(1)†O(1)††médio; O(n) no pior caso (colisões)
Conjunto (set)-O(1)† pertenceO(1)†O(1)†itens únicos, sem repetição
Árvore de busca balanceadaO(log n)O(log n)O(log n)O(log n)mantém ordem; O(n) se desbalanceia
Heap (fila de prioridade)O(1) ver topoO(n)O(log n)O(log n) topomenor ou maior sempre no topo

Array x lista ligada: o trade-off entre acesso e inserção

Array e lista ligada guardam sequências, mas por dentro são opostos. O array reserva um bloco contíguo de memória, então calcular onde está o item de posição 5 é conta direta: acesso por índice em O(1). O preço é a inserção no meio, que empurra todos os elementos seguintes uma casa para o lado, custando O(n). A lista ligada guarda cada item em um nó que aponta para o próximo, espalhados pela memória. Inserir ou remover é só religar dois ponteiros, O(1), desde que você já esteja no ponto certo. A conta vira: para chegar ao ponto certo, ela percorre desde o começo, O(n). Ou seja, um é rápido para ler por posição e caro para remexer no meio; o outro é o inverso. Na maioria dos códigos do dia a dia a lista dinâmica (o list do Python, o array do JavaScript) vence, porque acesso por índice e inserção no fim cobrem quase tudo. Você aprende a lista ligada por dentro para entender o custo, mas raramente a implementa à mão.

Tabela hash: por que o dicionário busca em O(1) (e quando degrada)

O dicionário é a estrutura que mais muda a vida de quem programa, porque transforma busca de O(n) em O(1). O truque é a função hash: ela pega a chave (um nome, um CPF, um id) e calcula um número que aponta para o endereço onde o valor mora. Guardar e buscar viram ir direto ao local, sem varredura. Por isso "achar o telefone da Ana" custa o mesmo com dez contatos ou com dez milhões. O detalhe honesto é o pior caso: se muitas chaves geram o mesmo endereço (colisão), a estrutura precisa percorrer uma pequena lista interna para desempatar, e no limite isso vira O(n). Implementações sérias, como a do Python, redimensionam a tabela e distribuem bem as chaves, então na prática você trata dicionário e conjunto como O(1) médio. O conjunto (set) é a mesma máquina sem os valores: serve para responder "esse item já apareceu?" em O(1) e para remover duplicados de uma coleção.

Pilha (LIFO) e fila (FIFO): dois padrões que aparecem sempre

Pilha e fila guardam itens em sequência, mas mudam quem sai primeiro. A pilha é LIFO: o último a entrar é o primeiro a sair, como uma pilha de pratos onde você tira o de cima. Ela aparece no desfazer e refazer de editores, na navegação de voltar do navegador e na própria máquina, que empilha as chamadas de funções e desempilha ao retornar. A fila é FIFO: o primeiro a entrar é o primeiro a sair, como a fila do caixa. Ela aparece em tarefas processadas por ordem de chegada, filas de impressão e no percurso em largura de um grafo. As duas fazem inserir e remover em O(1), e é justamente essa garantia de custo fixo nas pontas que as torna úteis. Em Python, a pilha é o próprio list (append e pop no fim), e a fila é o deque do módulo collections, que remove da frente em O(1), coisa que o list não faz bem.

Árvores: por que a busca fica O(log n)

A árvore organiza dados em uma hierarquia de nós, cada um com filhos. A mais usada para busca é a árvore binária de busca: em todo nó, os menores ficam à esquerda e os maiores à direita. Procurar um valor vira uma sequência de comparações em que cada passo descarta metade do que sobrou, e cortar pela metade repetidamente é exatamente o que produz O(log n). Uma árvore balanceada com um milhão de itens tem cerca de vinte níveis, então você acha qualquer valor em vinte comparações. O senão é o balanceamento: se os dados entram já ordenados e ninguém reequilibra, a árvore cresce toda para um lado, vira uma lista disfarçada e a busca degrada para O(n). Para garantir o O(log n) existem árvores que se ajustam sozinhas, como AVL e rubro-negra. Árvores também estão por trás de índices de banco de dados e de estruturas ordenadas que precisam de inserção e busca rápidas ao mesmo tempo.

Busca linear x busca binária

Achar um item é a operação mais comum da computação, e há duas estratégias base. A busca linear confere item por item e funciona em qualquer coleção, ordenada ou não, ao custo de O(n). A busca binária é bem mais rápida, mas cobra um preço de entrada: os dados precisam estar ordenados. Ela olha o item do meio, compara com o alvo e descarta a metade onde ele não pode estar, repetindo até achar, em O(log n). É a mesma tática de adivinhar um número de 1 a 100 chutando sempre o meio: você acerta em no máximo sete tentativas. Veja as duas em Python.

def busca_linear(lista, alvo):
    # O(n): confere item por item, serve para lista sem ordem
    for i, valor in enumerate(lista):
        if valor == alvo:
            return i
    return -1

def busca_binaria(ordenada, alvo):
    # O(log n): exige a lista JA ORDENADA
    inicio, fim = 0, len(ordenada) - 1
    while inicio <= fim:
        meio = (inicio + fim) // 2
        if ordenada[meio] == alvo:
            return meio
        if ordenada[meio] < alvo:
            inicio = meio + 1   # descarta a metade de baixo
        else:
            fim = meio - 1      # descarta a metade de cima
    return -1

A lição prática: se você vai buscar muitas vezes na mesma coleção, ordenar uma vez (O(n log n)) e depois usar busca binária compensa. Se vai buscar por chave e não por ordem, nem ordene: jogue os dados em um dicionário e busque em O(1).

Algoritmos de ordenação: quais existem e quando usar

Ordenar é reorganizar uma coleção segundo um critério, e é pré-requisito para busca binária e mil relatórios. Os algoritmos didáticos (bubble, insertion, selection) são O(n²): fáceis de entender, lentos com dados grandes. Os de produção (merge, quick e o Timsort do Python) são O(n log n). Um fato importante e provado: nenhum algoritmo de ordenação baseado em comparação consegue ser melhor que O(n log n) no melhor caso possível, esse é o piso teórico. A tabela compara os principais.

AlgoritmoMelhorMédioPiorEspaçoQuando usar
Bubble sortO(n)O(n²)O(n²)O(1)só para aprender o conceito
Insertion sortO(n)O(n²)O(n²)O(1)listas pequenas ou quase ordenadas
Selection sortO(n²)O(n²)O(n²)O(1)quando trocar itens custa muito caro
Merge sortO(n log n)O(n log n)O(n log n)O(n)desempenho garantido e ordenação estável
Quick sortO(n log n)O(n log n)O(n²)O(log n)rápido na prática, ordena no lugar
Timsort (sort do Python)O(n)O(n log n)O(n log n)O(n)o padrão: use sorted() e list.sort()

A conclusão para o dia a dia é curta: não escreva seu próprio sort em produção. O sorted() e o list.sort() do Python usam Timsort, que detecta trechos já ordenados e ganha desempenho em dados reais. Você implementa merge e quick à mão para entender a divisão e conquista, não para usar no lugar da biblioteca.

Recursão x iteração

Recursão é uma função que resolve um problema chamando a si mesma em uma versão menor, até chegar a um caso base que para a cadeia. Iteração resolve o mesmo com laços. Os dois têm o mesmo poder: todo algoritmo recursivo pode virar laço e vice-versa. A recursão brilha em problemas que se dividem naturalmente, como percorrer árvores, merge sort e backtracking, deixando o código curto e parecido com a definição do problema. O laço costuma gastar menos memória, porque não empilha uma chamada por nível, e não corre risco de estourar a pilha em profundidades grandes. O exemplo clássico do fatorial mostra os dois lados.

def fatorial_recursivo(n):
    if n <= 1:          # caso base que para a recursao
        return 1
    return n * fatorial_recursivo(n - 1)

def fatorial_iterativo(n):
    resultado = 1
    for k in range(2, n + 1):
        resultado *= k
    return resultado

# fatorial_recursivo(5) == fatorial_iterativo(5) == 120

Escolha pela clareza: se a solução recursiva fica óbvia e a profundidade é controlada, use recursão. Se a profundidade pode ser enorme (percorrer uma lista de milhões), prefira o laço para não estourar a pilha de chamadas.

Complexidade de tempo x complexidade de espaço

Big O mede duas coisas diferentes com a mesma notação. Complexidade de tempo conta os passos executados; complexidade de espaço conta a memória extra usada além dos dados de entrada. As duas às vezes competem. O merge sort é O(n log n) no tempo, mas gasta O(n) de espaço, porque copia os dados em listas auxiliares. O quick sort ordena quase sem memória extra, apenas O(log n) da pilha de recursão. A memoização, que guarda resultados já calculados para não repetir trabalho, é o exemplo mais direto do troca: ela derruba o tempo (transforma exponencial em linear) gastando espaço para armazenar as respostas. Em ambientes com memória apertada, um algoritmo um pouco mais lento porém enxuto pode ser a escolha certa. Sempre olhe as duas dimensões antes de decidir.

Como escolher a estrutura certa para o problema

A decisão fica simples quando você parte da operação mais frequente e escolhe a estrutura que a torna barata. Precisa buscar rápido por uma chave única? Use dicionário, busca em O(1). Precisa saber se um item já apareceu ou remover duplicados? Use conjunto. Precisa manter ordem de chegada e processar do começo? Use fila. Precisa do último item primeiro (desfazer, voltar)? Use pilha. Precisa sempre do menor ou maior a cada passo? Use heap. Precisa dos dados ordenados com inserção e busca rápidas? Use árvore balanceada. Precisa só de uma sequência acessada por posição? A lista basta. O erro mais caro é forçar a lista em tudo por ser a mais familiar. Vale também escolher pelo que não muda: uma tupla (imutável) comunica que aquele conjunto de valores não deve ser alterado, e o interpretador aproveita isso.

Estruturas de dados em Python: list, dict, set, tuple e collections

O Python já traz as estruturas centrais prontas, e conhecer o custo de cada uma evita surpresa. O list é a lista dinâmica (acesso O(1), append no fim O(1) amortizado, insert no meio O(n), item in list é O(n)). O dict é a tabela hash (get, set e del em O(1) médio). O set é o conjunto (pertence, adicionar e remover em O(1) médio). A tuple é a sequência imutável, boa para dados fixos. O módulo collections completa o kit com o deque (fila com pontas em O(1), o jeito certo de fazer fila, já que list.insert(0, x) é O(n)), o Counter (contagem de itens) e o defaultdict (chave nova já nasce com um valor padrão).

numeros = [10, 20, 30]        # list: ordem e acesso por indice
numeros.append(40)            # O(1) amortizado no fim

agenda = {"ana": "1234"}      # dict: busca por chave em O(1) medio
agenda["bruno"] = "5678"

vistos = {1, 2, 3}            # set: itens unicos, "in" em O(1) medio
print(2 in vistos)            # True

ponto = (10, 20)             # tuple: imutavel, comunica dado fixo

from collections import deque, Counter, defaultdict
fila = deque()                # fila eficiente: popleft em O(1)
fila.append("a")
fila.popleft()

contagem = Counter("banana")  # Counter({'a': 3, 'n': 2, 'b': 1})
grupos = defaultdict(list)    # chave nova ja nasce com []
grupos["frutas"].append("uva")

Para praticar sem instalar nada, o guia de como aprender Python do zero aponta o caminho, e a guia de listas em Python aprofunda a estrutura que você mais vai usar. Quem quiser ver estas estruturas em dados reais pode brincar com a ferramenta de formatar JSON, que exibe listas e dicionários aninhados na tela.

Erros comuns de quem sai do básico

O primeiro é usar sempre a lista, mesmo quando o dicionário resolveria: procurar um contato em uma lista de milhares varre tudo (O(n)), enquanto o dicionário acha na hora (O(1)). O segundo é aninhar laços sobre dados grandes sem perceber que o custo vira O(n²): dobrar os dados quadruplica o trabalho. O terceiro é rodar busca linear em dados que já dariam para ordenar e usar busca binária, ou pior, ordenar a coleção inteira a cada consulta em vez de uma vez só. O quarto é confundir acesso por posição (O(1)) com busca por valor (O(n)) e achar que a lista busca rápido. E o quinto é confiar na entrada do usuário sem validar, a maior fonte de erros e brechas de segurança. Conhecer esses tropeços de antemão é meia batalha ganha.

Limitações deste guia

As complexidades aqui são o comportamento assintótico típico de cada estrutura e algoritmo: descrevem como o custo cresce, não o tempo exato em segundos, que depende da máquina, da linguagem e da implementação. Casos médios (como O(1) do dicionário) são estatísticos e têm pior caso pior; casos amortizados (append no fim da lista) valem na média de muitas operações, não em toda operação isolada. Este é um guia de fundamentos: estruturas mais especializadas (tries, tabelas de espalhamento com sondagem, árvores B, grafos ponderados) e a análise formal de algoritmos ficam para o estudo avançado. Para aprender com prática guiada, jogos e um laboratório de algoritmos, o curso intermediário de lógica cobre este conteúdo passo a passo.

Fontes oficiais

Para conferir as complexidades e aprofundar, use material de referência reconhecido, não blogs de terceiros:

Conclusão

Estruturas de dados e algoritmos são o passo que transforma quem escreve pequenos programas em quem resolve problemas de verdade, em qualquer linguagem. O resumo cabe em uma frase: conheça o custo de cada estrutura, escolha a que torna barata a operação que você faz mais, e deixe a biblioteca ordenar por você. Depois deste guia, avance para as estruturas de dados avançadas (pilha, fila, árvore, grafo e hash por dentro), firme a base com o guia de como aprender programação do zero e entenda o conceito raiz na guia sobre o que é um algoritmo. Quem estuda Python pode aprofundar com programação orientada a objetos e decorators em Python. Para praticar com dados reais, use a calculadora de Base64, o conversor de binário, decimal e hexadecimal e a calculadora de hash (MD5, SHA). Veja também como o portal audita seus números na página como validamos os cálculos e conheça as demais ferramentas da categoria Tecnologia. Para aprender tudo com método, comece pelo Curso de Lógica de Programação Intermediário e veja todos os cursos gratuitos do ValorFinal.

Calculadoras deste guia

Leia também

Fontes oficiais

Links externos para os documentos oficiais consultados na construção desta página. O conteúdo deles pode mudar sem aviso; em caso de divergência, vale sempre a fonte oficial.

Como validamos os cálculos

Os valores citados neste guia são estimativos e baseados em fontes oficiais (Documentação oficial do Python e MDN Web Docs). Eles podem variar conforme convenção coletiva, situação individual e atualizações da legislação. Entenda nossa metodologia em como validamos os cálculos.

Perguntas frequentes

O que são estruturas de dados, em palavras simples?
São jeitos organizados de guardar muitos dados juntos para que o programa consiga achá-los e usá-los com facilidade. Pense em objetos de escritório: uma pilha de papéis, uma gaveta com pastas etiquetadas, um fichário. Cada um guarda documentos, mas de um jeito que facilita um tipo de busca. Estruturas de dados são isso para a informação de um programa: lista, dicionário, conjunto, pilha, fila e árvore, cada uma boa para um tipo de problema. Escolher a estrutura certa muda operações caras em operações baratas.
O que é Big O sem complicação?
É uma forma de dizer como o custo de um algoritmo cresce quando os dados aumentam, contando passos em vez de segundos. Um teste mental resolve: se você dobrar a quantidade de dados, o trabalho não muda (constante, O(1)), soma só um passo (logarítmico, O(log n)), dobra (linear, O(n)) ou quadruplica (quadrático, O(n²))? A busca binária adiciona um passo quando os dados dobram, por isso acha um item entre um milhão em cerca de vinte passos. Não precisa de fórmula pesada, só dessa intuição.
Buscar um valor em um array não ordenado é O(log n)?
Não. Em um array (ou lista) sem ordem, achar um valor específico é O(n): no pior caso você percorre todos os elementos, porque não há como saber onde ele está sem olhar. O(log n) só vale quando os dados estão ordenados e você usa busca binária, que corta a lista ao meio a cada passo. Acesso por posição (lista[5]) é O(1), mas isso é diferente de procurar por valor. Confundir acesso por índice com busca por valor é um erro comum.
Por que o dicionário é O(1) e a lista é O(n) para buscar?
O dicionário (tabela hash) calcula, a partir da chave, o endereço onde o valor está guardado. Ele vai direto ao local, sem varrer nada, e por isso a busca é O(1) em média. A lista não tem esse cálculo: para achar um valor por conteúdo, ela confere item por item, o que é O(n). A diferença aparece com dados grandes: procurar um contato entre um milhão em uma lista custa até um milhão de comparações; no dicionário, custa uma. É por isso que quem busca por chave usa dicionário.
Quando o dicionário deixa de ser O(1)?
No pior caso, a tabela hash vira O(n). Isso acontece quando muitas chaves caem no mesmo endereço (colisão) e a estrutura precisa percorrer uma lista interna para desempatar. Implementações boas, como a do Python, redimensionam a tabela e distribuem bem as chaves, então o pior caso quase nunca aparece no dia a dia. Na prática você trata dicionário e conjunto como O(1) médio, mas é bom saber que a garantia é estatística, não absoluta.
Qual a diferença entre pilha e fila?
As duas guardam itens em sequência, mas a ordem de saída muda. A pilha é LIFO (last in, first out): o último a entrar é o primeiro a sair, como uma pilha de pratos. A fila é FIFO (first in, first out): o primeiro a entrar é o primeiro a sair, como a fila do banco. Pilha aparece em desfazer/refazer e na volta de funções; fila aparece em tarefas processadas em ordem de chegada. Ambas fazem inserir e remover em O(1).
Por que a busca em árvore é O(log n)?
Em uma árvore binária de busca balanceada, cada comparação descarta metade dos itens restantes: se o alvo é menor que o nó, você desce à esquerda e ignora todo o lado direito. Cortar pela metade a cada passo é exatamente o que produz O(log n). O detalhe importante é balanceada: se a árvore fica torta (todos os nós de um lado), ela vira uma lista disfarçada e a busca degrada para O(n). Por isso existem árvores que se reequilibram sozinhas, como AVL e rubro-negra.
Qual algoritmo de ordenação é o mais rápido?
Não existe um vencedor único, mas para dados gerais os métodos O(n log n) ganham: merge sort e quick sort. O merge sort garante O(n log n) sempre e é estável, ao custo de memória extra O(n). O quick sort costuma ser mais rápido na prática e ordena no lugar, mas tem pior caso O(n²) se os pivôs forem ruins. Nenhum algoritmo baseado em comparação consegue passar de O(n log n) no melhor caso possível, isso é um limite matemático provado. Na dúvida, use o sort da sua linguagem: o Python usa Timsort, otimizado para dados reais.
Qual a diferença entre complexidade de tempo e de espaço?
Tempo mede quantos passos o algoritmo executa; espaço mede quanta memória extra ele precisa além dos dados de entrada. Os dois usam a mesma notação Big O, mas medem coisas diferentes e às vezes competem. O merge sort é O(n log n) no tempo, porém gasta O(n) de espaço; o quick sort ordena quase sem memória extra (O(log n) da pilha de recursão). Em máquinas com pouca memória, um algoritmo mais lento porém enxuto pode ser a escolha certa. Sempre olhe as duas dimensões.
Recursão é melhor ou pior que laço?
Depende do problema, não é questão de melhor ou pior. Recursão deixa o código mais curto e claro em estruturas que se dividem naturalmente (árvores, merge sort, backtracking). O laço costuma gastar menos memória, porque não empilha chamadas, e evita o erro de estouro de pilha quando a profundidade é grande. Todo algoritmo recursivo pode ser reescrito com laço e vice-versa. Escolha pela clareza: se a solução recursiva fica óbvia e a profundidade é limitada, use recursão; senão, itere.
Preciso de matemática avançada para aprender algoritmos?
Não para começar. Busca, ordenação e a noção de eficiência se entendem com desenhos e exemplos do dia a dia, sem cálculo pesado. O que importa é o raciocínio: por que cortar uma lista ordenada ao meio economiza tanto esforço, por que dois laços aninhados ficam caros, quando um dicionário vence uma lista. A matemática formal existe em estudos avançados de análise de algoritmos, mas não é pré-requisito para dominar o essencial que se usa no trabalho.
Depois de estruturas de dados e algoritmos, qual o próximo passo?
O passo seguinte são as estruturas avançadas: lista ligada, árvore, grafo e tabela hash por dentro, além dos algoritmos BFS, DFS, backtracking e memoização. Vale também firmar uma linguagem de verdade, e o Python é uma boa escolha porque a escrita é próxima do pseudocódigo. Com o raciocínio de estruturas e Big O na cabeça, a linguagem vira só a ferramenta: você já sabe pensar a solução, falta apenas escrevê-la.