Trilha de aprendizado · Nível 4 · Tutorial 7

Resolver problemas com recursão e casos base

Implemente soluções recursivas simples, acompanhe suas chamadas e garanta que cada etapa avance até um caso base explícito.

  • Nível: Intermediário
  • Duração: 20 min
  • 7 passos
Resolver problemas com recursão e casos base

O que você vai percorrer

  1. Definir o problema menor e o caso base Planeje a soma recursiva delimitando o domínio, definindo uma resposta imediata e escolhendo uma redução que chegue ao caso base. 3 min
  2. Transformar o plano em uma função recursiva Implemente a soma de 1 até n com um retorno para o caso base e outro para o passo recursivo. 3 min
  3. Acompanhar a descida das chamadas e a volta dos resultados Rastreie uma execução curta da soma recursiva, observando estados locais, operações pendentes e retornos. 4 min
  4. Encontrar falhas no caminho até o caso base Inspecione sequências curtas de argumentos para diagnosticar reduções que não alcançam o caso base e corrigir a função sem esconder o defeito. 3 min
  5. Reconhecer o limite de profundidade Interprete RecursionError e diferencie uma falha de progresso de uma sequência recursiva correta, porém profunda demais. 2 min
  6. Escolher entre recursão e laço Compare duas soluções para a mesma soma e escolha a abordagem considerando clareza e profundidade esperada. 2 min
  7. Aplicar e revisar o raciocínio recursivo Transfira o raciocínio recursivo para o cálculo de potências, confira sua implementação e revise os critérios de uma solução que termina corretamente. 4 min

O que você vai aprender

  • Definir o domínio de entrada, o caso base e a redução de um problema recursivo simples.
  • Implementar uma função que chame a si mesma e devolva o resultado corretamente.
  • Rastrear os argumentos, os estados locais e os retornos de uma sequência curta de chamadas.
  • Identificar ausência de progresso até o caso base e reconhecer quando um laço é uma alternativa mais adequada.

Antes de começar

  • Decompor um programa em funções com responsabilidades claras
  • Controlar o escopo e o estado das funções
  • Construir contadores e acumuladores em laços

Passo 1 de 7

Definir o problema menor e o caso base

Planeje a soma recursiva delimitando o domínio, definindo uma resposta imediata e escolhendo uma redução que chegue ao caso base.

Pensar em uma versão menor

O que torna uma solução recursiva?

Na recursão direta, uma função resolve o problema chamando a si mesma para uma versão menor do mesmo problema.

Nosso exemplo será a soma dos inteiros de 1 até n. Antes de pensar no código, defina o contrato:

  • Domínio: n é um inteiro não negativo.
  • Resultado esperado: a soma de 1 até n.

Assim, para n = 4, o resultado é 1 + 2 + 3 + 4 = 10. A precondição delimita quando a solução deve funcionar; entradas negativas não pertencem ao domínio declarado.

Exemplo

O problema menor preserva a tarefa

Para calcular a soma até 4, separe o valor atual do restante:

S(4) = 4 + S(3)

O problema S(3) é menor, mas continua sendo a mesma tarefa: somar os inteiros de 1 até um limite.

Chegar a uma resposta imediata

Caso base, redução e progresso

Uma definição recursiva precisa de duas decisões:

  • Caso base: situação cuja resposta já é conhecida, sem nova chamada. Para n = 0, a soma é 0.
  • Passo recursivo: redução para um problema menor. Quando n > 0, use n - 1.

A sequência 4 → 3 → 2 → 1 → 0 chega ao caso base. Chamamos n de medida de progresso: é o valor que diminui a cada redução e permite verificar que o processo se aproxima do encerramento.

Reduções sucessivas até o caso base

Acompanhe como o tamanho restante do problema diminui de uma etapa para a seguinte.

Diagrama em degraus mostrando os valores 4, 3, 2, 1 e 0 ligados por setas, com o zero destacado como caso base.

Cada redução subtrai uma unidade de n; em n = 0, a resposta é imediata e não há nova redução.

Verifique o plano

Domínio, caso base e redução coerentes

Qual plano descreve corretamente a soma dos inteiros de 1 até n e garante que a redução alcance o caso base?

Passo 2 de 7

Transformar o plano em uma função recursiva

Implemente a soma de 1 até n com um retorno para o caso base e outro para o passo recursivo.

Do plano ao código

Dois caminhos de retorno

A função segue o plano definido anteriormente:

  • Se n == 0, return 0 encerra a chamada antes de qualquer nova chamada.
  • Caso contrário, soma_ate(n - 1) resolve o problema menor.
  • A expressão n + ... combina o valor atual com o resultado desse subproblema.

Os dois caminhos usam return, pois ambos precisam entregar um resultado ao código que chamou a função.

Implementação da soma recursiva

O primeiro return resolve o caso base. O segundo reduz o argumento e compõe a resposta.

python
def soma_ate(n: int) -> int:
    """Retorna a soma de 1 até n.

    Precondição: n é um inteiro não negativo.
    """
    if n == 0:
        return 0

    return n + soma_ate(n - 1)

Estrutura dos dois caminhos

Diagrama em que uma condição divide a função entre um caminho curto que termina imediatamente e outro que reduz o problema antes de combinar o resultado.

O caminho base termina sem nova chamada; o caminho recursivo reduz o problema e usa o resultado devolvido.

Execute no seu computador

Conferência com entradas pequenas

Copie o programa completo para seu editor e execute-o. Antes de conferir a saída, preveja o resultado de cada chamada. Observe que soma_ate(0) usa somente o caminho base, enquanto as outras chamadas também usam o caminho recursivo.

Programa completo

Execute este código com Python 3 no seu computador.

python
def soma_ate(n: int) -> int:
    """Retorna a soma de 1 até n.

    Precondição: n é um inteiro não negativo.
    """
    if n == 0:
        return 0

    return n + soma_ate(n - 1)


print(soma_ate(0))
print(soma_ate(1))
print(soma_ate(4))

Exemplo

Saída esperada

0
1
10

Se o resultado for diferente, confira se há um return em cada caminho e se a nova chamada recebe n - 1.

Complete a redução

Passo recursivo

Complete apenas o argumento da chamada recursiva:

return n + soma_ate(____)

Preveja os resultados

Caso base e entrada pequena

Quais valores são devolvidos, respectivamente, por soma_ate(0) e soma_ate(3)?

Passo 3 de 7

Acompanhar a descida das chamadas e a volta dos resultados

Rastreie uma execução curta da soma recursiva, observando estados locais, operações pendentes e retornos.

Chamadas que ainda não terminaram

Uma pilha de chamadas pendentes

Em soma_ate(3), uma chamada precisa do resultado da seguinte antes de concluir sua soma. Durante a descida:

  • cada chamada recebe seu próprio valor local de n;
  • o valor das chamadas anteriores não é substituído;
  • a chamada atual fica suspensa com uma operação pendente.

Assim, soma_ate(3) aguarda 3 + soma_ate(2), enquanto soma_ate(2) aguarda 2 + soma_ate(1). A pilha de chamadas representa essas chamadas abertas que ainda não terminaram.

Função rastreada

Para uma entrada pequena, acompanhe primeiro as chamadas e depois os retornos.

python
def soma_ate(n: int) -> int:
    """Retorna a soma de 1 até n.

    Precondição: n é um inteiro não negativo.
    """
    if n == 0:
        return 0

    return n + soma_ate(n - 1)


print(soma_ate(3))  # 6

Descida e volta

O caso base inicia a volta

A descida abre as chamadas com argumentos 3 → 2 → 1 → 0. Em soma_ate(0), o caso base devolve 0 sem abrir outra chamada.

Começa então a volta, em ordem inversa:

  • soma_ate(1) resolve 1 + 0 e devolve 1;
  • soma_ate(2) resolve 2 + 1 e devolve 3;
  • soma_ate(3) resolve 3 + 3 e devolve 6.

Cada retorno fornece o valor necessário para resolver uma operação que estava pendente.

Duas fases da execução

Compare a abertura das chamadas com a resolução das somas.

Diagrama dividido em descida e volta: blocos com valores locais 3, 2, 1 e 0 são empilhados; depois, resultados 0, 1, 3 e 6 sobem em ordem inversa.

Na descida, as operações ficam pendentes. Na volta, os resultados percorrem as chamadas em ordem inversa.

Confira o rastreamento

Ordene chamadas e retornos

Coloque os eventos de soma_ate(3) na ordem em que acontecem.

  1. `soma_ate(3)` calcula `3 + 3` e devolve `6`.
  2. `soma_ate(3)` abre `soma_ate(2)` e deixa `3 + ...` pendente.
  3. `soma_ate(1)` calcula `1 + 0` e devolve `1`.
  4. `soma_ate(2)` abre `soma_ate(1)` e deixa `2 + ...` pendente.
  5. `soma_ate(1)` abre `soma_ate(0)` e deixa `1 + ...` pendente.
  6. `soma_ate(2)` calcula `2 + 1` e devolve `3`.
  7. `soma_ate(0)` devolve `0`.

Estado de uma chamada intermediária

Durante soma_ate(3), qual descrição de soma_ate(2) está correta?

Passo 4 de 7

Encontrar falhas no caminho até o caso base

Inspecione sequências curtas de argumentos para diagnosticar reduções que não alcançam o caso base e corrigir a função sem esconder o defeito.

Três caminhos defeituosos

Inspecione antes de executar

Uma função recursiva precisa ter um caminho que devolva um resultado sem nova chamada. Além disso, cada chamada recursiva deve aproximar uma entrada válida do caso base.

Para diagnosticar uma falha, anote apenas os primeiros argumentos. Não execute uma cadeia que você já percebeu que não termina.

Variantes da soma

Considere o mesmo contrato: n é um inteiro não negativo e o resultado deve ser a soma de 1 até n. Inspecione as funções sem executar chamadas que não terminam.

python
def soma_a(n: int) -> int:
    return n + soma_a(n - 1)


def soma_b(n: int) -> int:
    if n == 0:
        return 0

    return n + soma_b(n)


def soma_c(n: int) -> int:
    if n == 0:
        return 0

    return n + soma_c(n - 2)

Siga os argumentos

O progresso precisa alcançar o caso base

A sequência de argumentos revela defeitos diferentes:

  • soma_a(3): 3 → 2 → 1 → 0 → -1... — não existe um caso base que encerre a chamada.
  • soma_b(3): 3 → 3 → 3... — o argumento permanece igual.
  • soma_c(3): 3 → 1 → -1 → -3... — a redução parece diminuir, mas salta o caso base 0. Além disso, diminuir de dois em dois não representa a soma de todos os inteiros.

A entrada 3 pertence ao domínio declarado. Portanto, essas falhas violam o contrato. Já chamar a implementação correta com n = -1 estaria fora do domínio documentado e seria outro problema.

Comparação dos caminhos

Três sequências de nós: uma atravessa zero sem parar por não ter caso base, outra repete o mesmo valor e a terceira salta de um valor positivo para um negativo sem tocar zero.

Observe se há uma parada explícita e se cada redução realmente chega até ela para todas as entradas válidas.

Dica

Não mova o alvo para esconder o erro

A correção deve preservar o domínio e o resultado esperado. Trocar o caso base apenas para interromper uma redução inadequada pode esconder a falha sem calcular a soma correta. Neste problema, a redução coerente continua sendo n - 1.

Associe cada defeito

Diagnóstico das variantes

Associe cada função ao defeito principal.

Toque em um item e depois no par correspondente.

Corrija e demonstre o progresso

Repare soma_c

Corrija apenas o passo recursivo de soma_c para calcular a soma de 1 até n. Depois, mostre a sequência de argumentos a partir de n = 3 e explique por que ela alcança o caso base.

Escreva pelo menos 30 caracteres (0/30).

Passo 5 de 7

Reconhecer o limite de profundidade

Interprete RecursionError e diferencie uma falha de progresso de uma sequência recursiva correta, porém profunda demais.

O que RecursionError informa

Chamadas aninhadas têm um limite

O Python limita a profundidade das chamadas aninhadas. Quando uma execução recursiva ultrapassa o limite disponível, ela é interrompida com RecursionError.

Esse erro pode ocorrer mesmo quando existem um caso base correto e uma redução que progride até ele. Uma entrada muito grande pode exigir mais chamadas simultâneas do que o ambiente permite.

A profundidade disponível depende do ambiente e do contexto da execução. Portanto, não suponha um número universal de chamadas.

Exemplo

Reconheça a mensagem

Uma execução pode terminar com uma mensagem semelhante a esta:

Traceback (most recent call last):
  ...
RecursionError: maximum recursion depth exceeded

A última linha identifica o tipo do erro. Isoladamente, ela informa que a profundidade máxima foi excedida, mas não revela se houve uma falha lógica ou apenas chamadas corretas em quantidade excessiva.

Duas causas possíveis

Examine o caminho dos argumentos

Para interpretar o erro, verifique alguns argumentos sucessivos:

  • Se o argumento permanece igual ou se afasta do caso base, há uma falha lógica de progresso.
  • Se o argumento se aproxima corretamente do caso base, mas a entrada exige uma cadeia muito longa, a lógica pode estar correta e a profundidade ser o problema.

Assim, RecursionError é um sintoma. O caminho das chamadas ajuda a identificar a causa.

Sem progresso ou profundo demais

Compare uma cadeia que não se aproxima do encerramento com outra que progride, mas encontra o limite antes de chegar ao caso base.

Comparação lado a lado entre chamadas repetidas sem progresso e chamadas que diminuem corretamente, mas atingem uma barreira de profundidade.

À esquerda, a medida do problema não muda. À direita, ela diminui, mas a quantidade de chamadas aninhadas ultrapassa o limite disponível.

Atenção

Não esconda uma falha lógica

Aumentar o limite de profundidade não corrige uma função cujo argumento não progride até o caso base. Primeiro confirme o domínio, o caso base e a sequência de reduções. Mesmo com a lógica correta, uma solução que exige chamadas demais pode pedir outra abordagem.

Interprete antes de concluir

O que o erro permite afirmar?

A função soma_ate possui o caso base n == 0, usa n - 1 e funciona para entradas pequenas. Se uma entrada muito maior produzir RecursionError, isso prova que o caso base está ausente.

Passo 6 de 7

Escolher entre recursão e laço

Compare duas soluções para a mesma soma e escolha a abordagem considerando clareza e profundidade esperada.

Duas soluções para a mesma soma

Mesmo contrato, estruturas diferentes

As duas funções abaixo calculam a soma dos inteiros de 1 até n, considerando n um inteiro não negativo. A versão recursiva expressa diretamente a definição n + soma_ate(n - 1). A versão iterativa percorre os valores e mantém o resultado parcial em um acumulador.

Versão recursiva

Cada chamada deixa uma soma pendente até que o caso base seja alcançado.

python
def soma_ate(n: int) -> int:
    """Retorna a soma de 1 até n.

    Precondição: n é um inteiro não negativo.
    """
    if n == 0:
        return 0

    return n + soma_ate(n - 1)

Versão iterativa

O acumulador é atualizado a cada repetição e devolvido ao final.

python
def soma_ate_iterativa(n: int) -> int:
    """Retorna a soma de 1 até n.

    Precondição: n é um inteiro não negativo.
    """
    total = 0

    for valor in range(1, n + 1):
        total += valor

    return total

Chamadas pendentes ou acumulador

Como o trabalho é organizado

Na recursão, soma_ate(3) abre chamadas para 2, 1 e 0. As operações ficam pendentes e são resolvidas durante a volta. No laço, uma única chamada atualiza total sucessivamente: 0 → 1 → 3 → 6.

A recursão pode ser mais clara quando o problema já é definido naturalmente por versões menores de si mesmo. Para esta soma linear, porém, o laço representa bem a repetição e não cria uma cadeia de chamadas aninhadas.

Duas formas de organizar a soma

Comparação entre uma pilha de chamadas recursivas com somas pendentes e um percurso iterativo com um único acumulador.

À esquerda, várias chamadas permanecem abertas; à direita, uma única chamada atualiza o resultado parcial.

Dica

Não escolha pela aparência da sintaxe

Recursão não é automaticamente melhor nem mais rápida. Compare a clareza da solução com a profundidade esperada. Para uma soma que pode exigir muitos milhares de etapas, o laço geralmente é mais adequado porque evita uma cadeia profunda de chamadas.

Decida com base no problema

Dois critérios práticos

Pergunte: a definição recursiva torna o raciocínio mais claro? E quantas chamadas podem ficar aninhadas? Uma entrada pequena pode tornar a versão recursiva fácil de acompanhar. Se a mesma tarefa linear puder exigir uma quantidade muito grande de etapas, a versão iterativa evita o limite de profundidade sem alterar o resultado.

Escolha justificada

Uma equipe usa a soma de 1 até n em dois contextos: uma demonstração com n pequeno e um processamento que pode receber valores muito grandes. Qual avaliação é mais adequada?

Passo 7 de 7

Aplicar e revisar o raciocínio recursivo

Transfira o raciocínio recursivo para o cálculo de potências, confira sua implementação e revise os critérios de uma solução que termina corretamente.

Implemente uma potência recursiva

Um novo problema, o mesmo raciocínio

Crie no seu editor a função potencia(base, expoente) para calcular uma potência por multiplicações sucessivas.

O contrato será:

  • Domínio: base é um inteiro positivo e expoente é um inteiro não negativo.
  • Resultado: base elevado a expoente.
  • Caso base: qualquer base elevada a zero resulta em 1.
  • Redução esperada: a nova chamada deve receber um expoente uma unidade menor.

No passo recursivo, combine a base atual com o resultado do subproblema. Não use ** nem um laço nesta primeira implementação.

Chamadas para conferência

Escreva a função no espaço indicado e execute o programa completo no seu computador.

python
def potencia(base: int, expoente: int) -> int:
    """Retorna base elevada a expoente.

    Precondições: base é um inteiro positivo e expoente é
    um inteiro não negativo.
    """
    # Escreva aqui o caso base e o passo recursivo.


print(potencia(2, 0))  # esperado: 1
print(potencia(5, 1))  # esperado: 5
print(potencia(3, 4))  # esperado: 81

Dica

Confira antes de comparar

Preveja os três resultados, execute seu código e investigue qualquer diferença. Verifique principalmente se o caso base usa return e se o passo recursivo reduz o expoente.

Registre seu raciocínio

Implementação, rastreamento e limites

Depois de executar sua solução, registre:

  1. O código completo da função e os três resultados observados.
  2. As chamadas abertas por potencia(3, 2) e os valores devolvidos durante a volta.
  3. A medida de progresso e por que ela alcança o caso base para todo expoente do domínio.
  4. O que aconteceria se a redução fosse expoente - 2, mantendo apenas o caso base expoente == 0, para a entrada expoente = 3.
  5. Por que um laço pode ser mais adequado quando o expoente pode ser muito grande.

Escreva pelo menos 180 caracteres (0/180).

Compare com a referência

Conferência da solução

Agora compare sua implementação com a referência. O expoente é a medida de progresso: ele diminui de um em um até zero. Na volta, cada chamada multiplica a base pelo resultado recebido.

Para potencia(3, 2), as chamadas recebem os expoentes 2 → 1 → 0; os retornos são 1 → 3 → 9. Se a redução fosse de duas unidades, o expoente 3 seguiria 3 → 1 → -1... e saltaria o único caso base.

Solução de referência completa

Compare o caso base, o argumento da chamada recursiva e a composição do retorno.

python
def potencia(base: int, expoente: int) -> int:
    """Retorna base elevada a expoente.

    Precondições: base é um inteiro positivo e expoente é
    um inteiro não negativo.
    """
    if expoente == 0:
        return 1

    return base * potencia(base, expoente - 1)


print(potencia(2, 0))  # 1
print(potencia(5, 1))  # 5
print(potencia(3, 4))  # 81

Critérios de uma recursão correta

Use este esquema para revisar qualquer solução recursiva simples: o contrato delimita as entradas, o caso base encerra uma chamada, a redução garante progresso e a composição produz a resposta durante a volta.

Diagrama visual com domínio de entrada, caso base, reduções sucessivas, composição dos retornos e decisão entre recursão e laço.

Uma solução recursiva precisa ser correta no caso base, avançar em todas as entradas do domínio e ter profundidade adequada.

Revisão final

Resumo

Checklist do raciocínio recursivo

Antes de considerar uma solução recursiva concluída, revise estes pontos.

  • Declare o domínio de entrada e o resultado esperado.
  • Defina um caso base que devolva um resultado sem nova chamada.
  • Escolha uma medida de progresso e reduza-a em direção ao caso base.
  • Componha e devolva o resultado do subproblema com return.
  • Rastreie entradas pequenas para conferir chamadas, estados locais e retornos.
  • Se a cadeia linear puder ser muito profunda, considere um laço em vez de aumentar o limite de recursão.

Tutorial concluído

Parabéns! Você concluiu: Resolver problemas com recursão e casos base

Você concluiu “Resolver problemas com recursão e casos base”. Agora consegue identificar o contrato, o caso base, a redução, a composição dos retornos e os limites práticos de uma solução recursiva.

100 XP

Baixe o Aplicativo agora para ter acesso a + de 5000 cursos gratuitos, exercícios, certificado e muito conteúdo sem pagar nada!

  • Cursos online 100% gratuitos do início ao fim

    Milhares de cursos online em vídeo, ebooks e áudiobooks.

  • Mais de 60 mil exercícios gratuitos

    Para testar seus conhecimentos no decorrer dos cursos online

  • Certificado Digital gratuito válido em todo o Brasil

    Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail

Aplicativo Cursa na tela de ebook, na tela de curso em vídeo e na tela de exercícios do curso, mais o certificado de conclusão de curso