TL;DR
- Le Problème: Optimisation de la complexité temporelle et spatiale des structures de données.
- L'Approche: Problème style CTCI 3.5 pour débutants: trier une pile pour que les plus petites valeurs soient au sommet. Une seule pile extra. Raisonnement insertion sort en Java clair.
- Complexité: Compromis optimal entre temps et mémoire avec gestion des cas limites.
Tu as une pile d'assiettes en désordre. Tu ne peux soulever que l'assiette du dessus, et on te laisse une seule table d'appoint vide. Tu veux l'assiette la plus légère au sommet à la fin (la plus petite valeur en haut). Tu ne peux pas les aligner par terre. Pas de troisième pile. Cette contrainte, c'est tout l'énigme de sort stack.
Ce billet est un enseignement original pour débutants en Java. Même famille de questions d'entretien sur le tri par piles, pas une copie de livre. Fait partie de la série CTCI en Java. Chapitre 3, piles et files.
1. Analogie du quotidien
Imagine deux piles de cartes numérotées:
- Pile source
s: le tas désordonné que tu dois laisser trié (tu rechargessavec la réponse à la fin). - Pile temporaire
r: ta seule table d'appoint. Elle tient des cartes déjà dans un ordre trié qui grandit. - Tu n'as droit qu'à push, pop et peek sur le sommet. Pas de tableaux, pas de listes, pas de maps.
Le truc ressemble à insertion sort. Tu tires une carte de s. Tu gares sur s les cartes plus grandes de r jusqu'à ce qu'elle tienne. Tu la poses sur r. Tu répètes. Quand s est vide, tu verses r sur s pour obtenir l'ordre voulu.
2. Problème en mots simples
Entrée: une pile d'entiers (ou de valeurs comparables). Le sommet est ce que renvoie pop.
Sortie: la même pile, triée pour que les valeurs les plus petites soient au sommet. Les grandes descendent vers le fond.
Règles:
- Tu peux utiliser une pile temporaire supplémentaire.
- Tu ne peux pas utiliser de tableaux, listes chaînées, arbres ou autres collections comme buffer.
- Tu peux utiliser des constantes et quelques variables locales (la valeur que tu tiens en main).
Exemples (la valeur la plus à droite est le sommet):
| Avant (fond → sommet) | Après (fond → sommet) | Sommet final |
|---|---|---|
3, 1, 4, 2 |
4, 3, 2, 1 |
1 |
5 |
5 |
5 |
| vide | vide | n/a |
2, 2, 1 |
2, 2, 1 |
1 |
1, 2, 3 (sommet 3) |
3, 2, 1 |
1 |
Si fond→sommet est 1, 2, 3, le sommet est 3 (le plus grand). Après tri, fond→sommet est 3, 2, 1 donc le sommet est 1 (le plus petit).
À clarifier avant de coder:
- Plus petit au sommet, ou plus grand? (Ici: plus petit au sommet.)
- Doublons autorisés? (Oui. Pas d'exigence de stabilité entre égaux.)
- Récursion? La récursion est une pile implicite. On veut souvent la version itérative avec une pile temporaire explicite.
- Muter la pile donnée ou en renvoyer une autre? Muter en remplissant
sà la fin.
3. Réfléchir d'abord (insertion sort avec pile temporaire)
Ce que tu ne peux pas faire
Tout vider dans un tableau, appeler Arrays.sort, réempiler. Ça casse la règle "pas d'autres structures".
Idée d'insertion
Garde la pile temporaire r triée avec le plus grand au sommet (et le plus petit au fond de r). Ensuite:
- Dépile
tmpdes. - Tant que
rn'est pas vide etr.peek() > tmp, dépile deret repousse ces valeurs surs. Elles sont trop grandes pour rester soustmpsurr. - Pousse
tmpsurr.ra encore le plus grand au sommet parmi son contenu actuel. - Répète jusqu'à ce que
ssoit vide. - Vide
rsurs. Chaque pop met le suivant plus grand surs, donc à la fin le plus petit est au sommet des.
Pourquoi regarer les grandes valeurs sur s? Parce que tu n'as qu'une pile temporaire. La pile source est le seul parking légal. Ces valeurs seront réinsérées plus tard, comme insertion sort revisite des éléments.
Parcours: fond → sommet 3, 1, 4, 2 (sommet = 2)
| Étape | tmp |
Action | s (fond → sommet) |
r (fond → sommet) |
|---|---|---|---|---|
| début | 3, 1, 4, 2 |
vide | ||
| 1 | 2 | r vide, push 2 |
3, 1, 4 |
2 |
| 2 | 4 | 2 > 4? non, push 4 |
3, 1 |
2, 4 |
| 3 | 1 | 4 > 1, gare 4 sur s; 2 > 1, gare 2 sur s; push 1 |
3, 4, 2 |
1 |
| 4 | 2 | 1 > 2? non, push 2 |
3, 4 |
1, 2 |
| 5 | 4 | 2 > 4? non, push 4 |
3 |
1, 2, 4 |
| 6 | 3 | 4 > 3, gare 4 sur s; 2 > 3? non, push 3 |
4 |
1, 2, 3 |
| 7 | 4 | 3 > 4? non, push 4 |
vide | 1, 2, 3, 4 |
| copie | verse r → s |
4, 3, 2, 1 |
vide |
Sommet de s = 1. Terminé.
4. Solution Java
Utilise java.util.Stack pour l'enseignement, ou tout type LIFO avec push, pop, peek, isEmpty.
import java.util.Stack;
/**
* Sorts stack so smallest values end on top.
* Uses one temporary stack. Insertion-sort style moves.
*/
void sortStack(Stack<Integer> s) {
Stack<Integer> r = new Stack<Integer>();
while (!s.isEmpty()) {
int tmp = s.pop();
// Park larger values back onto s so tmp can sit on r.
while (!r.isEmpty() && r.peek() > tmp) {
s.push(r.pop());
}
r.push(tmp);
}
// r has largest on top. Reverse onto s so smallest ends on top.
while (!r.isEmpty()) {
s.push(r.pop());
}
}
Si l'énoncé demande le plus grand au sommet, inverse la comparaison en r.peek() < tmp et revois la copie finale, ou trie pour plus petit au sommet puis inverse avec les deux mêmes piles. Confirme l'ordre à voix haute avant de coder.
Petit pilote:
Stack<Integer> s = new Stack<Integer>();
s.push(3);
s.push(1);
s.push(4);
s.push(2); // top is 2
sortStack(s);
// pop order: 1, 2, 3, 4
5. Tableau de complexité
| Approche | Temps | Espace extra | Notes |
|---|---|---|---|
| Pile temporaire (style insertion) | O(N²) pire cas | O(N) pour la pile temporaire | Chacune des N valeurs peut aller et venir souvent |
| Déjà presque trié (chance) | proche de O(N) | O(N) | Peu de garages si l'ordre aide |
| Tableau + sort (interdit ici) | O(N log N) | O(N) | Casse la règle d'une seule pile extra |
N est le nombre d'éléments. Le pire cas ressemble à une entrée "à l'envers" avec beaucoup de garages. L'espace extra est la deuxième pile jusqu'à N éléments, plus O(1) locaux. Avec seulement du LIFO, tu ne descends pas sous O(N) d'espace auxiliaire si tu dois tout réordonner.
6. Cas limites et erreurs fréquentes
Les interviewers testent ceci:
- Pile vide → les deux boucles ne font rien. OK.
- Un seul élément → pop vers
r, push verss. Correct. - Tous égaux →
r.peek() > tmpn'est jamais vrai avec un>strict. Les doublons restent. Bien. - Déjà le plus petit au sommet → on peut quand même repasser par
r. La correction d'abord; la sortie anticipée est optionnelle. - Sommets strictement décroissants → beaucoup de garages. Toujours O(N²) et correct.
- Négatifs et zéros → la comparaison marche pareil avec
Integer.
Erreurs fréquentes:
- Mauvaise comparaison.
r.peek() < tmpconstruit l'ordre inverse surr. Tu finis avec le plus grand au sommet desaprès la copie, ou un chaos si tu mélanges les conditions. - Oublier le versement final. Laisser la réponse sur
réchoue si l'appelant regarde encores. - Un autre type de buffer. Un
ArrayListcomme parking viole l'énoncé même si le code "marche". - Comparer après un pop sans peek. Fais peek (ou garde la valeur) avant de décider de déplacer de
rverss. - Boucle infinie. Si tu repousses
tmpsurspar erreur dans la boucle externe sans progrès, tu tournes. Gardetmpen local jusqu'à ce qu'il atterrisse surr.
Entrée sûre si l'API accepte une pile nulle:
void sortStackSafe(Stack<Integer> s) {
if (s == null) {
return;
}
sortStack(s);
}
7. Récap à raconter à un ami
Sort stack demande: réordonne une pile pour que les plus petites valeurs soient au sommet, avec une seule pile extra.
- Tiens une pile temporaire
r. Fais-la grandir avec le plus grand au sommet der. - Dépile une valeur
tmpde la pile d'entrée. - Tant que le sommet de
rest plus grand quetmp, gare ces grandes valeurs sur la pile d'entrée. - Pousse
tmpsurr. Répète jusqu'à vider l'entrée. - Verse
rsur la pile d'entrée. L'inversion laisse le plus petit au sommet.
C'est insertion sort déguisé en pile. Temps O(N²), espace extra O(N) pour la pile d'aide. Vide, un élément et doublons sortent des mêmes boucles.
Si tu peux le dire en trente secondes, croquer le mouvement garage-et-insertion, et ne pas "tricher" avec un tableau, tu maîtrises le problème 3.5.
Série
- Guide: Guide de la série CTCI
- Précédent: Queue via Stacks
- Suivant: Animal Shelter
