Probleme – La plus longue sous-suite croissante, de à
Exercice · niveau 3 (difficile) · informatique (MP2I/MPI), chapitre 17 — Programmation dynamique
Énoncé
- Écrire l'algorithme dynamique en , avec son invariant.
- En donner un en , et expliquer ce que contient le tableau qu'il maintient.
- Ce tableau est-il une sous-suite croissante du tableau d'entrée ?
Corrigé
1. La version dynamique. Sous-problème : est la longueur de la plus longue sous-suite strictement croissante se terminant en . Le « se terminant en » est l'idée de tout le problème : sans lui, on ne sait pas recoller.
(* Longueur de la plus longue sous-suite strictement croissante.
Complexite : Theta(n^2) en temps, Theta(n) en memoire. *)
let lis_quadratique a =
let n = Array.length a in
let l = Array.make n 1 in
for i = 0 to n - 1 do
(* INVARIANT : pour tout k < i, l.(k) est la longueur maximale
d'une sous-suite croissante se terminant en a.(k). *)
for j = 0 to i - 1 do
if a.(j) < a.(i) && l.(j) + 1 > l.(i) then l.(i) <- l.(j) + 1
done
done;
Array.fold_left max 0 l
La réponse est le maximum des , et non : la plus longue sous-suite ne finit pas nécessairement au dernier élément. C'est la faute classique de l'exercice.
2. La version en . On maintient un tableau où est la plus petite valeur qui puisse terminer une sous-suite croissante de longueur parmi les éléments déjà lus. Ce tableau est strictement croissant — c'est ce qui autorise la dichotomie.
(* Meme specification, en Theta(n log n). *)
let lis_nlogn a =
let n = Array.length a in
let q = Array.make n 0 in
let taille = ref 0 in
Array.iter (fun x ->
(* premier indice k tel que q.(k) >= x, par dichotomie *)
let g = ref 0 and d = ref !taille in
while !g < !d do
let mi = (!g + !d) / 2 in
if q.(mi) < x then g := mi + 1 else d := mi
done;
q.(!g) <- x; (* on ecrase, ou on allonge *)
if !g = !taille then incr taille) a;
!taille
Pourquoi reste croissant. On remplace par seulement si (c'est le premier tel indice) et : la valeur baisse à sa place, l'ordre est préservé. Et taille ne croît que d'une unité à la fois, quand dépasse toutes les queues connues.
Mesure, les deux versions sur les mêmes entrées :
a = [3;1;4;1;5;9;2;6;5;3;5;8;9;7;9] quadratique = 6 n log n = 6
b = [2;5;3;4;1] quadratique = 3 n log n = 3
c = [10;9;2;5;3;7;101;18] quadratique = 4 n log n = 4
3. Non, et c'est le piège. Sur , le tableau vaut à la fin . Or apparaît après et dans : n'est pas une sous-suite de . Sa longueur, , est pourtant la bonne — une plus longue sous-suite croissante est .
Ce qu'il faut en retenir : n'est pas une solution, c'est un certificat de longueur. Pour rendre le mot, il faut mémoriser, pour chaque élément inséré, l'indice de son prédécesseur dans au moment de l'insertion, puis remonter — exactement le procédé de reconstruction du chapitre, appliqué à une table qui n'a pas la forme d'une table. Un algorithme rapide qui rend la bonne valeur ne rend pas pour autant la solution, et c'est vrai bien au-delà de cet exemple.
Les autres exercices de ce chapitre Le cours du chapitre
Un blocage sur cet exercice ? Le tuteur d'Adloun guide par questions, sans donner la réponse.