0Pricing
Coding Interview Prep · Aula

Soma-alvo com sinais positivos e negativos

Transforme o problema de atribuição da soma-alvo em uma mochila baseada na diferença entre somas de subconjuntos, resolvendo-o em tempo O(n × sum).

Soma-alvo com sinais positivos e negativos é uma aula grátis de Coding Interview Prep no CoddyKit. Esta é a aula 4 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de Coding Interview Prep, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de Coding Interview Prep inclui 4 aulas no total.

O Problema da Soma-alvo

Dado um vetor de inteiros nums e um inteiro target, atribua um sinal + ou - a cada número para que a expressão resultante tenha valor target. Retorne o número de maneiras distintas de fazer isso. Por exemplo, com nums=[1,1,1,1,1] e target=3, existem 5 maneiras (escolher 4 elementos para serem positivos e 1 para ser negativo, em posições diferentes).

Força Bruta: Enumeração com DFS

Uma abordagem com DFS atribui + ou - a cada número e faz chamadas recursivas, retornando a contagem dos nós folha que alcançam target. Está correta, mas tem complexidade de tempo O(2^n) — exponencial. Para n=20, são mais de um milhão de chamadas recursivas. Vale a pena mencionar primeiro a abordagem com DFS e, em seguida, passar rapidamente à otimização com DP.

def findTargetSumWays_dfs(nums, target):
    count = [0]
    
    def dfs(i, current_sum):
        if i == len(nums):
            if current_sum == target:
                count[0] += 1
            return
        dfs(i+1, current_sum + nums[i])
        dfs(i+1, current_sum - nums[i])
    
    dfs(0, 0)
    return count[0]

print(findTargetSumWays_dfs([1,1,1,1,1], 3))  # 5

DFS com Memoização

Adicione memoização à DFS: o estado é (index, current_sum). Como current_sum pode variar de -total a +total, há O(n × total) estados únicos. Com memoização, a DFS é executada em tempo e espaço O(n × total). Isso funciona e é válido em entrevistas, mas o DP baseado em transformação é mais elegante e eficiente em termos de espaço.

from functools import lru_cache

def findTargetSumWays_memo(nums, target):
    total = sum(nums)
    
    @lru_cache(maxsize=None)
    def dp(i, remaining):
        if i == len(nums):
            return 1 if remaining == 0 else 0
        return dp(i+1, remaining - nums[i]) + dp(i+1, remaining + nums[i])
    
    return dp(0, target)

print(findTargetSumWays_memo([1,1,1,1,1], 3))  # 5

Transformação Matemática

Seja P o conjunto dos números aos quais foi atribuído + e N o conjunto dos que receberam -. Então: sum(P) - sum(N) = target e sum(P) + sum(N) = total. Somando: 2 × sum(P) = target + total, portanto sum(P) = (target + total) / 2. O problema reduz-se a: conte os subconjuntos dos números cuja soma seja (target + total) / 2. Esta é exatamente a variante de “contagem de subconjuntos” da mochila 0/1.

# sum(P) - sum(N) = target
# sum(P) + sum(N) = total
# => 2*sum(P) = target + total
# => sum(P) = (target + total) / 2
# Count subsets with sum = new_target = (target + total) // 2
print('Reduction: count subsets summing to (target + total) // 2')

Verificações de Validade Antes do DP

Antes de executar o DP, verifique: (1) target + total deve ser par (caso contrário, sum(P) não é um inteiro — impossível); (2) abs(target) > total significa que o alvo não pode ser alcançado, mesmo que todos os sinais estejam alinhados. Se alguma verificação falhar, retorne 0 imediatamente. Essas verificações tratam os casos extremos de forma clara, sem condições especiais dentro do laço do DP.

def findTargetSumWays(nums, target):
    total = sum(nums)
    if (target + total) % 2 != 0:
        return 0  # sum(P) would be non-integer
    if abs(target) > total:
        return 0  # impossible to reach
    new_target = (target + total) // 2
    # Count subsets summing to new_target
    dp = [0] * (new_target + 1)
    dp[0] = 1
    for num in nums:
        for c in range(new_target, num - 1, -1):
            dp[c] += dp[c - num]
    return dp[new_target]

print(findTargetSumWays([1,1,1,1,1], 3))  # 5

Acompanhamento de um Exemplo Pequeno

Para nums=[1,1,1,1,1], target=3: total=5, new_target=(3+5)//2=4. Contamos subconjuntos cuja soma seja 4 em [1,1,1,1,1]. Isso é C(5,4)=5 (escolha quatro números 1 para serem positivos, e o quinto para ser negativo: 1+1+1+1-1=3). O DP retorna corretamente 5. A transformação mapeia elegantemente o problema de atribuição de sinais para um problema padrão de contagem de subconjuntos.

Tratamento de Zeros nos Números

Se nums contiver zeros, atribuir + ou - a um zero não altera a soma. Cada zero duplica o número de atribuições válidas. O DP trata isso naturalmente: ao processar num=0, o laço interno range(new_target, -1, -1) vai de new_target até 0, e dp[c] += dp[c - 0] = dp[c] duplica todas as somas alcançáveis. Não é necessário nenhum tratamento especial se você usar range(new_target, num-1, -1), que começa em new_target e vai até 0 quando num=0.

# With zeros: each zero doubles the count
print(findTargetSumWays([0, 0, 1], 1))  # 4
# Assignments: +0+0+1, +0-0+1, -0+0+1, -0-0+1 = all give sum 1

Comparação de Complexidade

A DFS de força bruta é O(2^n). A DFS com memoização é O(n × total) em tempo e O(n × total) em espaço. O DP 1D baseado na transformação é O(n × new_target) em tempo e O(new_target) em espaço, em que new_target ≤ total. O DP 1D usa significativamente menos espaço que a memoização porque descarta a dimensão do índice por meio da transformação.

Relação com Outros Problemas de Mochila

Soma-alvo reúne vários conceitos de mochila: começa como um problema de atribuição, transforma-se em soma de subconjuntos (como Partição de Soma de Subconjuntos Iguais) e usa o mesmo modelo de iteração reversa da mochila 0/1, mas com contagem (como em Troco II). Dominar essas relações permite classificar rapidamente novos problemas em entrevistas pela semelhança estrutural com padrões conhecidos.

Casos Extremos e Observações para Entrevistas

Casos principais: (1) target = total: apenas uma maneira (todos positivos); (2) target = -total: apenas uma maneira (todos negativos); (3) target = 0 com todos os elementos iguais a zero: a resposta é 2^n; (4) total muito grande, mas n pequeno — o tamanho do vetor de DP é limitado por total/2. Em entrevistas, explique verbalmente a transformação antes de programar — é a ideia não óbvia que diferencia candidatos fortes.

Alternativa de DP 2D sem Transformação

Sem a transformação, defina dp[i][s] = número de maneiras de atribuir sinais aos primeiros i números para alcançar a soma s. A soma pode ser negativa, então desloque-a por total: use dp[i][s + total]. Isso exige uma tabela 2D de tamanho (n+1) × (2*total+1). Embora correta, ela usa mais espaço e é mais difícil de programar rapidamente sob a pressão de uma entrevista do que a mochila 1D após a transformação.

Verificação Rápida

Teste sua compreensão dos conceitos de Estruturas de Dados & Algoritmos — Preparação para Entrevistas de Programação desta lição.

Recapitulação da Lição

Nesta lição, você aprendeu: Soma-alvo transforma a atribuição de sinais na contagem de subconjuntos cuja soma é (target + total) / 2, a iteração reversa da mochila 0/1 1D conta subconjuntos em tempo O(n × new_target) e espaço O(new_target) e verificações antecipadas de validade (soma ímpar, |target| > total) evitam a execução desnecessária do DP. Em seguida, entraremos no território dos caminhos mais curtos com o algoritmo de Dijkstra e uma fila de prioridade.

Perguntas Frequentes

A aula “Soma-alvo com sinais positivos e negativos” é grátis?

Sim — o texto completo de “Soma-alvo com sinais positivos e negativos” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de Coding Interview Prep, atualize para CoddyKit PRO. O curso de Coding Interview Prep inclui 4 aulas no total.

O que vou aprender em “Soma-alvo com sinais positivos e negativos”?

Transforme o problema de atribuição da soma-alvo em uma mochila baseada na diferença entre somas de subconjuntos, resolvendo-o em tempo O(n × sum). Você pratica Coding Interview Prep com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.

Preciso ter experiência prévia para começar Coding Interview Prep?

Nenhuma experiência prévia é necessária. Coding Interview Prep no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 4 de 4.

Quanto tempo leva a aula “Soma-alvo com sinais positivos e negativos”?

A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.

Posso escrever e executar código nesta aula de Coding Interview Prep?

Sim. Cada aula de Coding Interview Prep inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.

Todas as aulas deste curso

  1. Mochila 0/1 e otimização de espaço
  2. Mochila ilimitada e troco de moedas II
  3. Soma de subconjunto com partição igual
  4. Soma-alvo com sinais positivos e negativos
← Voltar para Coding Interview Prep