Séance 1 — Comprendre le principe
Fibonacci : de la récursivité naïve à la programmation dynamique
1 fib_mem(n) — approche descendante
On mémorise chaque résultat dans un tableau memo au fur et à mesure des calculs.
Voir la réponse
def fib_mem(n):
memo = [0] * (n + 1)
def f_mem(k):
if k == 0 or k == 1:
memo[k] = k
return k
elif memo[k] > 0:
return memo[k]
else:
memo[k] = f_mem(k - 1) + f_mem(k - 2)
return memo[k]
return f_mem(n)
2 fib_asc(n) — approche ascendante
On construit le tableau des résultats de F0 à Fn, sans aucune récursivité.
Voir la réponse
def fib_asc(n):
if n < 2:
return n
tab = [0] * (n + 1)
tab[1] = 1
for i in range(2, n + 1):
tab[i] = tab[i - 1] + tab[i - 2]
return tab[n]
3 chemins(n, m) — dénombrement 2D
Nombre de chemins sur une grille n×m, en se déplaçant uniquement à droite ou en bas.
Voir la réponse
def chemins(n, m):
grille = [[0] * (m + 1) for _ in range(n + 1)]
for i in range(n + 1): grille[i][0] = 1
for j in range(m + 1): grille[0][j] = 1
for i in range(1, n + 1):
for j in range(1, m + 1):
grille[i][j] = grille[i-1][j] + grille[i][j-1]
return grille[n][m]
assert chemins(2, 2) == 6
🎯 Défi bonus
Comparer le nombre d'appels de fib_recursif et fib_mem pour n=25 (compteur global).
Séance 2 — Rendu de monnaie
L'un des deux exemples cités explicitement par le programme officiel
1 Version récursive naïve
Voir la réponse
def rendu_monnaie(pieces, s):
if s == 0:
return 0
r = s
for p in pieces:
if p <= s:
r = min(r, 1 + rendu_monnaie(pieces, s - p))
return r
2 Version ascendante
Voir la réponse
def rendu_monnaie_dyn(pieces, s):
nb = [0] * (s + 1)
for n in range(1, s + 1):
nb[n] = n
for p in pieces:
if p <= n:
nb[n] = min(nb[n], 1 + nb[n - p])
return nb[s]
3 Reconstruction de la solution optimale
Voir la réponse
def rendu_monnaie_solution(pieces, s):
nb = [n for n in range(s + 1)]
sol = [[1] * n for n in range(s + 1)]
sol[0] = []
for n in range(1, s + 1):
for p in pieces:
if p <= n and 1 + nb[n - p] < nb[n]:
nb[n] = 1 + nb[n - p]
sol[n] = sol[n - p].copy()
sol[n].append(p)
return sol[s]
💡 Glouton vs optimal
Avec {1, 3, 4} pour la somme 6 : le glouton prend 4+1+1 = 3 pièces, alors que 3+3 = 6 n'en nécessite que 2. Le glouton n'est pas toujours optimal !
🎯 Défi bonus — Le sac à dos (0/1)
Maximiser la valeur transportée sans dépasser une capacité W, chaque objet pris au plus une fois.
Voir la réponse
def sac_a_dos(poids, valeurs, W):
N = len(poids)
DP = [[0] * (W + 1) for _ in range(N + 1)]
for i in range(1, N + 1):
for w in range(W + 1):
DP[i][w] = DP[i-1][w]
if poids[i-1] <= w:
DP[i][w] = max(DP[i][w], valeurs[i-1] + DP[i-1][w - poids[i-1]])
return DP[N][W]
assert sac_a_dos([2,3,4], [3,4,5], 5) == 7
Séance 3 — Pyramide de nombres
Un exemple très progressif : représenter, valider, scorer, puis optimiser
1-2 est_chemin / score
Voir la réponse
def est_chemin(ch, p):
if len(ch) != len(p) or ch[0] != 0:
return False
for i in range(len(ch) - 1):
if not (ch[i+1] == ch[i] or ch[i+1] == ch[i] + 1):
return False
return True
def score(ch, p):
return sum(p[i][ch[i]] for i in range(len(p)))
3 calcule_score_max — récursif naïf
score_max(i,j) = p[i][j] + max(score_max(i+1,j), score_max(i+1,j+1)), cas de base au dernier niveau.
Voir la réponse
def calcule_score_max(p, i=0, j=0):
if i == len(p) - 1:
return p[i][j]
gauche = calcule_score_max(p, i+1, j)
droite = calcule_score_max(p, i+1, j+1)
return p[i][j] + max(gauche, droite)
4 prog_dyn — PD ascendante
Voir la réponse
def prog_dyn(p):
n = len(p)
s = [list(niveau) for niveau in p]
for i in range(n - 2, -1, -1):
for j in range(len(s[i])):
s[i][j] = p[i][j] + max(s[i+1][j], s[i+1][j+1])
return s[0][0]
🎯 Défi bonus — Coupe de barres d'acier
Maximiser le revenu en coupant une barre de longueur n, chaque longueur i ayant un prix pi.
Voir la réponse
def revenu_barre_dyn_asc(n):
prix = [0, 1, 5, 8, 9, 10, 17, 17, 20, 24, 30]
tab = [0] * (n + 1)
for m in range(1, n + 1):
r_max = float('-inf')
for i in range(1, m + 1):
r_max = max(r_max, prix[i] + tab[m - i])
tab[m] = r_max
return tab[n]
Séance 4 — Alignement de séquences
Le 2e exemple cité par le programme officiel (bio-informatique)
1 aligne(s1, s2)
sc[i][j] = score maximal d'alignement des i premières lettres de s1 avec les j premières de s2.
Voir la réponse
def aligne(s1, s2):
n1, n2 = len(s1), len(s2)
sc = [[0] * (n2 + 1) for _ in range(n1 + 1)]
for i in range(1, n1 + 1): sc[i][0] = -i
for j in range(1, n2 + 1): sc[0][j] = -j
for i in range(1, n1 + 1):
for j in range(1, n2 + 1):
s = max(-1 + sc[i-1][j], -1 + sc[i][j-1])
if s1[i-1] == s2[j-1]:
sc[i][j] = max(s, 1 + sc[i-1][j-1])
else:
sc[i][j] = max(s, -1 + sc[i-1][j-1])
return sc[n1][n2]
assert aligne("CHAT", "CAT") == 2
- C A T
- 0 -1 -2 -3
C -1 1 0 -1
H -2 0 0 0
A -3 -1 1 0
T -4 -2 0 2
2 affiche(s1, s2, sc)
Voir la réponse
def affiche(s1, s2, sc):
print(" ", end="")
for c in s2: print('{:>3}'.format(c), end="")
print()
for i in range(len(sc)):
print('{:>3}'.format(s1[i-1]) if i > 0 else " ", end="")
for v in sc[i]: print('{:>3}'.format(v), end="")
print()
🎯 Défis bonus
Bonus 1 — Reconstruire l'alignement optimal
Renvoyer, en plus du score, les deux chaînes alignées (avec '-' pour les gaps), via un second tableau sol.
Bonus 2 — Matrice de similarités (ADN)
Remplacer les scores fixes (+1/-1) par une matrice de similarités sim[car1][car2] et un coût de gap variable.
sim = {'A': {'A': 10, 'G': -1, 'C': -3, 'T': -4}, ...}
gap = -5