Coding Interview Prep · Leçon

Somme cible avec signes positifs et négatifs

Transformez le problème d’affectation target-sum en un sac à dos fondé sur la différence de sommes de sous-ensembles, et résolvez-le en O(n × sum).

Leçon 4 sur 413 étapes

Somme cible avec signes positifs et négatifs est une leçon Coding Interview Prep gratuite sur CoddyKit. Ceci est la leçon 4 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage Coding Interview Prep, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours Coding Interview Prep comprend 4 leçons au total.

Le problème de la somme cible

Étant donné un tableau d’entiers nums et un entier target, attribuez un signe + ou - à chaque nombre afin que l’expression obtenue s’évalue à target. Renvoyez le nombre de façons distinctes de procéder. Par exemple, avec nums=[1,1,1,1,1] et target=3, il existe 5 façons de faire (choisir 4 éléments positifs et 1 élément négatif, à des positions différentes).

Force brute : énumération par DFS

Une approche DFS attribue à chaque nombre le signe + ou - et effectue des appels récursifs, en renvoyant le nombre de nœuds feuilles qui atteignent target. Elle est correcte, mais sa complexité temporelle est O(2^n) — elle est donc exponentielle. Pour n=20, cela représente plus d’un million d’appels récursifs. Il est utile de commencer par mentionner l’approche DFS, puis de passer rapidement à l’optimisation par 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 avec mémoïsation

Ajoutez une mémoïsation à la DFS : l’état est (index, current_sum). Comme current_sum peut varier de -total à +total, il existe O(n × total) états distincts. Avec la mémoïsation, la DFS s’exécute en temps et en espace O(n × total). Cette approche fonctionne et reste valable en entretien, mais le DP fondé sur la transformation est plus élégant et plus économe en espace.

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

Transformation mathématique

Soit P l’ensemble des nombres auxquels le signe + est attribué, et N l’ensemble de ceux auxquels le signe - est attribué. Alors : sum(P) - sum(N) = target et sum(P) + sum(N) = total. En additionnant, on obtient : 2 × sum(P) = target + total, donc sum(P) = (target + total) / 2. Le problème se réduit à : compter les sous-ensembles de nombres dont la somme vaut (target + total) / 2. Il s’agit exactement de la variante « compter les sous-ensembles » du sac à dos 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')

Vérifications de validité avant le DP

Avant d’exécuter le DP, vérifiez les points suivants : (1) target + total doit être pair (sinon sum(P) n’est pas un entier, ce qui est impossible) ; (2) abs(target) > total signifie que la cible est impossible à atteindre, même si tous les signes sont orientés dans le même sens. Si l’une de ces vérifications échoue, renvoyez immédiatement 0. Ces vérifications traitent proprement les cas limites sans nécessiter de cas particuliers dans la boucle du 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

Suivi d’un petit exemple

Pour nums=[1,1,1,1,1], target=3 : total=5, new_target=(3+5)//2=4. Nous comptons les sous-ensembles dont la somme vaut 4 parmi [1,1,1,1,1]. Il s’agit de C(5,4)=5 (choisir 4 uns comme positifs et le cinquième comme négatif : 1+1+1+1-1=3). Le DP renvoie correctement 5. La transformation associe élégamment le problème d’attribution de signes à un problème standard de comptage de sous-ensembles.

Gestion des zéros dans le tableau de nombres

Si nums contient des zéros, attribuer le signe + ou - à un zéro ne modifie pas la somme. Chaque zéro double le nombre d’affectations valides. Le DP gère cela naturellement : lors du traitement de num=0, la boucle interne range(new_target, -1, -1) s’exécute de new_target jusqu’à 0, et dp[c] += dp[c - 0] = dp[c] double toutes les sommes atteignables. Aucun traitement particulier n’est nécessaire si vous utilisez range(new_target, num-1, -1), qui commence à new_target et descend jusqu’à 0 lorsque 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

Comparaison des complexités

La DFS par force brute est en O(2^n). La DFS avec mémoïsation utilise un temps et un espace O(n × total). Le DP 1D fondé sur la transformation s’exécute en temps O(n × new_target) et utilise un espace O(new_target), où new_target ≤ total. Le DP 1D utilise beaucoup moins d’espace que la mémoïsation, car la transformation élimine la dimension correspondant à l’indice.

Lien avec d’autres problèmes de sac à dos

La somme cible relie plusieurs concepts du sac à dos : elle commence comme un problème d’attribution, se transforme en somme de sous-ensembles (comme Partition Equal Subset Sum) et utilise le même modèle de parcours inverse du sac à dos 0/1, mais avec du comptage (comme dans Changement de monnaie II). Maîtriser ces liens permet de classer rapidement les nouveaux problèmes d’entretien en fonction de leur ressemblance structurelle avec des modèles connus.

Cas limites et remarques pour les entretiens

Cas importants : (1) target = total : une seule façon (tous les signes sont positifs) ; (2) target = -total : une seule façon (tous les signes sont négatifs) ; (3) target = 0 avec uniquement des zéros : la réponse est 2^n ; (4) total très élevé mais n petit — la taille du tableau de DP 1D est limitée par total/2. En entretien, expliquez oralement la transformation avant de coder : c’est l’idée non évidente qui distingue les meilleurs candidats.

Alternative de DP 2D sans transformation

Sans la transformation, définissez dp[i][s] comme le nombre de façons d’attribuer des signes aux i premiers nombres pour atteindre la somme s. La somme pouvant être négative, décalez-la de total : utilisez dp[i][s + total]. Cela nécessite un tableau 2D de taille (n+1) × (2*total+1). Bien que correcte, cette solution utilise davantage d’espace et est plus difficile à coder rapidement sous la pression d’un entretien que le sac à dos 1D obtenu après transformation.

Vérification rapide

Testez votre compréhension des concepts de structures de données & algorithmes — préparation aux entretiens de programmation présentés dans cette leçon.

Récapitulatif de la leçon

Dans cette leçon, vous avez appris : la somme cible transforme l’attribution de signes en un comptage de sous-ensembles dont la somme vaut (target + total) / 2, le parcours inverse du sac à dos 0/1 1D compte les sous-ensembles en temps O(n × new_target) et en espace O(new_target), et les vérifications de validité anticipées (somme impaire, |target| > total) évitent une exécution inutile du DP. Ensuite, nous entrerons dans le domaine des plus courts chemins avec l’algorithme de Dijkstra et une file de priorité.

Gratuit pour commencer

Apprends Coding Interview Prep avec un tuteur IA — gratuit

Écris et exécute du vrai code dans ton navigateur, obtiens de l'aide instantanée d'un tuteur IA disponible 24h/24, et reprends là où tu t'es arrêté sur le web ou dans l'app.

Cours
90
Leçons
360

Questions Fréquemment Posées

La leçon « Somme cible avec signes positifs et négatifs » est-elle gratuite ?

Oui — le texte complet de « Somme cible avec signes positifs et négatifs » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours Coding Interview Prep, passe à CoddyKit PRO. Le cours Coding Interview Prep comprend 4 leçons au total.

Qu'est-ce que j'apprendrai dans « Somme cible avec signes positifs et négatifs » ?

Transformez le problème d’affectation target-sum en un sac à dos fondé sur la différence de sommes de sous-ensembles, et résolvez-le en O(n × sum). Tu pratiques Coding Interview Prep avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.

Dois-je avoir de l'expérience pour commencer Coding Interview Prep ?

Aucune expérience préalable n'est requise. Coding Interview Prep sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 4 sur 4.

Combien de temps prend la leçon « Somme cible avec signes positifs et négatifs » ?

La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.

Peux-tu écrire et exécuter du code dans cette leçon Coding Interview Prep ?

Oui. Chaque leçon Coding Interview Prep inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.

Toutes les leçons de ce cours

  1. Sac à dos 0/1 et optimisation de l’espace
  2. Sac à dos illimité et rendu de monnaie II
  3. Partition en sous-ensembles de somme égale
  4. Somme cible avec signes positifs et négatifs
← Retour à Coding Interview Prep