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)) # 5DFS 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)) # 5Transformaçã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)) # 5Acompanhamento 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 1Comparaçã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
- Mochila 0/1 e otimização de espaço
- Mochila ilimitada e troco de moedas II
- Soma de subconjunto com partição igual
- Soma-alvo com sinais positivos e negativos