Unique Paths
Un robot commence dans la cellule en haut à gauche d’une grille comportant m lignes et n colonnes et doit atteindre la cellule en bas à droite. À chaque déplacement, il avance d’une cellule vers la droite ou d’une cellule vers le bas. Retourne le nombre de chemins différents qu’il peut emprunter.
Fonction
- minteger
- le nombre de lignes dans la grille
- ninteger
- le nombre de colonnes dans la grille
- Renvoieinteger
- le nombre de chemins différents entre la cellule en haut à gauche et la cellule en bas à droite
Contraintes
1 ≤ m, n ≤ 100- La réponse est au plus
2 × 109, elle tient donc dans un entier signé de 32 bits.
Exemples
- Entrée
- m = 3n = 4
- Sortie
- 10
- Explication
- Chaque chemin comporte 2 déplacements vers le bas et 3 déplacements vers la droite, soit 5 déplacements au total. Un chemin est déterminé par les 2 déplacements vers le bas parmi les 5, et il y a 10 façons de les choisir.
- Entrée
- m = 1n = 6
- Sortie
- 1
- Explication
- Avec une seule ligne, le robot ne peut se déplacer vers la droite que 5 fois, il n’y a donc qu’un seul chemin.
- Entrée
- m = 4n = 5
- Sortie
- 35
- Explication
- Chaque chemin comporte 3 déplacements vers le bas et 4 déplacements vers la droite. Choisir lesquels des 7 déplacements vont vers le bas donne 7 × 6 × 5 / 6 = 35 chemins.
+14 tests cachés à la soumission
Pour aller plus loin
Pour une grille de 100 × 100, la réponse comporte 59 chiffres. Comment la renverrais-tu modulo 10^9+7 à l’aide de la formule, alors que la division par i ne fonctionne plus ?
Indices
Ouvrez-les un par un. Chacun en dévoile un peu plus.
Où le robot aurait-il pu se trouver juste avant d’entrer dans une cellule ?
Les chemins menant à une cellule sont les chemins menant à la cellule au-dessus, plus les chemins menant à la cellule située à sa gauche. La rangée supérieure et la colonne de gauche n’ont chacune qu’un seul chemin.
Remplissez les décomptes ligne par ligne, de gauche à droite, en conservant une seule rangée de nombres. Ou comptez directement les ordres de déplacement : un chemin correspond au choix des
m-1déplacements vers le bas parmi lesm+n-2déplacements.
Solution
Énumérer les chemins un par un est impossible : une grille de 17 × 17 en compte déjà 601,080,390. Il faut les compter sans les énumérer. Les chemins qui mènent à une case sont les chemins qui mènent à la case située juste au-dessus, plus ceux qui mènent à la case juste à sa gauche : la grille devient ainsi un tableau que l’on remplit en un seul passage. Un chemin n’est rien d’autre qu’un ordre de déplacements vers le bas et vers la droite, ce qui donne une formule fermée.
Compter chaque chemin avec la récursion
Correcte, mais ne termine pas sur les plus gros tests
Intuition
Pense au dernier déplacement du robot vers la case en bas à droite. Il est soit descendu depuis la case au-dessus, soit venu de droite depuis la case à gauche, jamais les deux. Les chemins dans une grille de m × n sont donc les chemins dans la grille raccourcie d’une ligne, uniquePaths(m-1, n), plus les chemins dans la grille moins large d’une colonne, uniquePaths(m, n-1).
La récursion s’arrête lorsqu’il ne reste qu’une ligne ou une colonne dans la grille : le robot ne peut alors qu’avancer tout droit, il existe donc exactement 1 chemin. Chaque chemin se termine par l’un des deux déplacements, donc chaque chemin est compté une fois et le total est correct.
Cette méthode est lente, car chaque chemin se termine par un cas de base qui renvoie 1 : le nombre d’appels est donc au moins égal à la réponse elle-même. Une grille de 17 × 17 nécessite plus de 600 millions d’appels, et les tests vont jusqu’à des réponses proches de 1.6 × 10^9. Les mêmes grilles plus petites sont calculées de nombreuses fois : (m-1, n-1) est atteint une fois depuis chacun de ses deux parents, et les répétitions se multiplient à mesure que l’on descend.
Algorithme
- Si
mounvaut 1, renvoie 1 : le seul chemin est une ligne droite. - Sinon, compte les chemins dont le dernier déplacement va vers le bas,
uniquePaths(m-1, n). - Compte les chemins dont le dernier déplacement va vers la droite,
uniquePaths(m, n-1). - Renvoie leur somme.
def uniquePaths(m, n):
# One row or one column: the only path is a straight line
if m == 1 or n == 1:
return 1
# The last move came down from the row above or right from the column before
return uniquePaths(m - 1, n) + uniquePaths(m, n - 1)Remplissez la grille une ligne à la fois
Intuition
La récursion interroge les mêmes cellules encore et encore, alors qu’il n’y a que m × n cellules. Compte une seule fois les chemins vers chaque cellule, dans un ordre où les cellules dont tu as besoin sont toujours prêtes.
État : paths[r][c] est le nombre de chemins allant de la cellule en haut à gauche jusqu’à la ligne r, colonne c. Récurrence : paths[r][c] = paths[r-1][c] + paths[r][c-1], les chemins qui arrivent d’en haut plus ceux qui arrivent de la gauche. Cas de base : chaque cellule de la première ligne et de la première colonne a 1 chemin, en ligne droite. Ordre : ligne par ligne, de gauche à droite, afin que la cellule au-dessus et celle à gauche soient remplies avant que tu en aies besoin.
Pour m = 3 et n = 4, les lignes sont 1 1 1 1, puis 1 2 3 4, puis 1 3 6 10, et la réponse est la dernière cellule, 10.
Regarde maintenant ce que lit le remplissage : seulement la ligne au-dessus et la ligne que tu remplis. Garde donc une seule ligne. Avant de mettre à jour row[c], elle contient encore le compte de la ligne au-dessus, et row[c-1] contient déjà le nouveau compte à sa gauche ; ainsi, row[c] += row[c-1] correspond à toute la récurrence. La complexité temporelle reste O(m × n), et la mémoire passe de O(m × n) à O(n).
Algorithme
- Créez
rowavecnentrées, toutes égales à 1 : la rangée du haut. - Répétez
m-1fois, une fois pour chaque rangée sous celle du haut. - Dans chaque rangée, pour
cde 1 àn-1, ajoutezrow[c-1]àrow[c].row[0]reste égal à 1 : c'est la colonne de gauche. - Renvoyez
row[n-1].
def uniquePaths(m, n):
# row[c] counts the paths into column c of the current row.
# The top row is all 1s: the only way along it is straight right.
row = [1] * n
for _ in range(m - 1):
for c in range(1, n):
# Paths from above (the old row[c]) plus paths from the left (the new row[c-1])
row[c] += row[c - 1]
return row[n - 1]Compter les déplacements à l’aide d’un coefficient binomial
Intuition
Chaque chemin comporte exactement m-1 déplacements vers le bas et n-1 déplacements vers la droite, soit m+n-2 déplacements au total, dans un certain ordre. Chaque ordre correspond à un chemin valide : le robot ne fait jamais plus de m-1 déplacements vers le bas ni de n-1 déplacements vers la droite, donc il ne sort jamais de la grille. Un chemin revient donc à choisir lesquels des m+n-2 déplacements sont les m-1 déplacements vers le bas, et la réponse est le coefficient binomial C(m+n-2, m-1).
Le tableau de l’approche précédente est le triangle de Pascal tourné sur le côté, ce qui explique pourquoi les deux résultats concordent. Pour calculer le coefficient sans factorielle énorme, construis-le facteur par facteur. Avec N = m+n-2 et k = min(m, n)-1, multiplie par N-k+i, puis divise par i, pour i allant de 1 à k. Après l’étape i, la valeur intermédiaire est C(N-k+i, i), un entier, donc chaque division est exacte.
Pour m = 3 et n = 4 : N = 5, k = 2, et la valeur évolue ainsi : 1 × 4 / 1 = 4, puis 4 × 5 / 2 = 10. En choisissant le côté le plus court, la boucle comporte au plus 99 étapes. Le produit avant la dernière division est égal à k fois la réponse. Pour une grille de 17 × 17, cela donne 16 × 601,080,390, soit environ 9.6 × 10^9, ce qui dépasse la plage d’un entier signé sur 32 bits ; utilise donc un entier sur 64 bits.
Algorithme
- Définissez
N = m+n-2, le nombre de déplacements, etk = min(m, n)-1. - Initialisez un compteur sur 64 bits à 1.
- Pour
iallant de 1 àk, multipliez le compteur parN-k+i, puis divisez-le pari. - Retournez le compteur.
def uniquePaths(m, n):
# A path is m+n-2 moves; count the ways to choose which of them go down.
# Choose along the shorter side so the loop stays short.
moves = m + n - 2
k = min(m, n) - 1
count = 1
for i in range(1, k + 1):
# count goes from C(moves-k+i-1, i-1) to C(moves-k+i, i); the division is exact
count = count * (moves - k + i) // i
return count
Pièges et cas limites
Le calcul est court, donc les bogues se cachent aux bords de la grille et dans la taille des nombres.
- Calculer
(m+n-2)!et diviser par les deux autres factorielles provoque un dépassement bien avant que la réponse ne le fasse : 21! dépasse déjà la plage des nombres 64 bits, etm+n-2atteint 105 dans une grille de 100 × 7. - Diviser avant de multiplier, comme dans
count / i * (N-k+i), tronque le résultat, carcountn’est pas toujours un multiple dei. Multiplie d’abord : le produit est toujours divisible sans reste. - Le produit
count × (N-k+i)peut dépasser 2^31 même si la réponse ne le dépasse pas. Stocke-le dans un entier 64 bits. - Laisser la ligne du haut ou la colonne de gauche à 0 au lieu de 1 donne 0 dans toutes les cellules. Une grille avec une seule ligne ou une seule colonne comporte exactement 1 chemin.
- Échanger les lignes et les colonnes ne change pas la réponse, puisque
C(m+n-2, m-1) = C(m+n-2, n-1).
Questions fréquentes4
Quelle est la formule pour Unique Paths ?
La réponse est le coefficient binomial C(m+n-2, m-1). Chaque chemin comporte m-1 déplacements vers le bas et n-1 déplacements vers la droite, dans un certain ordre, et choisir lesquels des m+n-2 déplacements vont vers le bas détermine le chemin. Pour une grille de 3 × 4, C(5, 2) = 10.
Quelle est la complexité temporelle de Unique Paths ?
Le tableau de programmation dynamique prend un temps de O(m × n) et un espace de O(n) lorsque vous conservez une seule ligne. La formule binomiale prend un temps de O(min(m, n)) et un espace de O(1). La récursion simple effectue au moins autant d’appels qu’il y a de chemins, ce qui est exponentiel en m + n.
Comment résoudre le problème des chemins uniques lorsque certaines cellules sont bloquées ?
Utilisez le même tableau et définissez à 0 le nombre de chemins d’une cellule bloquée afin qu’aucun chemin ne la traverse. La ligne du haut et la colonne de gauche ne sont plus entièrement composées de 1 : chaque cellule située après une cellule bloquée dans la ligne du haut a 0 chemin. La formule ne fonctionne plus, car elle suppose que tous les ordres de déplacement sont autorisés.
Pourquoi le tableau des chemins uniques correspond-il au triangle de Pascal ?
Chaque cellule additionne la cellule au-dessus et celle à sa gauche, ce qui correspond à la règle de construction du triangle de Pascal, lu le long de ses diagonales. La cellule de la ligne r et de la colonne c contient C(r+c, r), donc la cellule en bas à droite contient C(m+n-2, m-1).
Problèmes similaires
Des problèmes qui reposent sur les mêmes idées. En résoudre deux ou trois, c’est ce qui ancre un schéma.
Python
def uniquePaths(m, n):
# Écrivez le code iciCas 1
Cas 2
Cas 3
Entrée
m = 3 n = 4
Attendu
10