Plus longue séquence consécutive et cache LRU
Résolvez longest-consecutive-sequence en O(n) à l’aide d’un ensemble, puis concevez un cache LRU avec un OrderedDict.
Plus longue séquence consécutive et cache LRU est une leçon DSA 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 DSA Interview Prep, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours DSA Interview Prep comprend 4 leçons au total.
Problème de la plus longue séquence consécutive
LeetCode 128 « Plus longue séquence consécutive » : étant donné un tableau non trié, trouvez la longueur de la plus longue séquence d’entiers consécutifs. Exemple : [100,4,200,1,3,2] contient la séquence consécutive [1,2,3,4], de longueur 4. La difficulté consiste à résoudre le problème en O(n) plutôt qu’en O(n log n), complexité obtenue avec un tri suivi d’un parcours.
L’idée essentielle est d’utiliser un ensemble pour effectuer des tests d’appartenance en O(1), et de commencer à compter une séquence uniquement à partir de son plus petit élément (identifié en vérifiant que son prédécesseur est absent de l’ensemble).
def longestConsecutive(nums):
num_set = set(nums)
best = 0
for n in num_set:
if n - 1 not in num_set: # n is the start of a sequence
curr_n = n
length = 1
while curr_n + 1 in num_set:
curr_n += 1
length += 1
best = max(best, length)
return best
print(longestConsecutive([100,4,200,1,3,2])) # 4
print(longestConsecutive([0,3,7,2,5,8,4,6,0,1])) # 9Pourquoi la preuve en O(n) est valide
Chaque nombre est parcouru au plus une fois dans la boucle tant que, sur l’ensemble des itérations de la boucle externe. Même si une boucle tant que se trouve à l’intérieur d’une boucle pour, le nombre total d’itérations de la boucle tant que sur toutes les itérations externes est au plus égal à n (car chaque nombre est le « curr_n + 1 » d’au plus une séquence). Cet argument amorti donne une complexité globale de O(n), comme dans l’analyse de la pile monotone.
# Demonstrate O(n) total inner iterations
nums = list(range(1000)) # worst case: one long sequence
num_set = set(nums)
inner_iters = 0
for n in num_set:
if n - 1 not in num_set:
curr = n
while curr + 1 in num_set:
curr += 1
inner_iters += 1
print('n =', len(nums), ' total inner iterations =', inner_iters)
# inner_iters = n-1 <= n => O(n)Alternative : approche fondée sur le tri
En comparaison, l’approche consistant à trier puis à parcourir s’exécute en O(n log n) : triez le tableau, éliminez les doublons consécutifs, puis comptez les séquences consécutives. Bien qu’elle soit plus lente, elle utilise un espace supplémentaire de O(1) (si le tri est effectué en place). L’approche avec un ensemble utilise un espace supplémentaire de O(n). Présentez les deux méthodes lors d’un entretien et précisez si la solution en O(n log n) est acceptable compte tenu des contraintes d’espace.
def longestConsecutive_sort(nums):
if not nums:
return 0
nums.sort()
best = length = 1
for i in range(1, len(nums)):
if nums[i] == nums[i-1]:
continue # skip duplicates
if nums[i] == nums[i-1] + 1:
length += 1
best = max(best, length)
else:
length = 1
return best
print(longestConsecutive_sort([100,4,200,1,3,2])) # 4Qu’est-ce qu’un cache LRU ?
Un cache LRU (élément utilisé le moins récemment) est une structure de données à capacité fixe qui expulse l’élément utilisé le moins récemment lorsqu’il est plein et qu’un nouvel élément doit être inséré. Opérations : get(key) renvoie la valeur si la clé existe (et la marque comme récemment utilisée), ou -1 si elle est absente ; put(key, value) insère la paire (en expulsant l’élément LRU si la capacité est atteinte).
Les caches LRU sont utilisés dans les systèmes d’exploitation (remplacement de pages), les caches des navigateurs et les caches de requêtes de bases de données. LeetCode 146 vous demande d’en implémenter un avec des opérations get et put en O(1).
Cache LRU avec OrderedDict
Le collections.OrderedDict de Python conserve l’ordre d’insertion et prend en charge move_to_end(key) (en O(1)) pour marquer un élément comme le plus récemment utilisé. Lors d’un put, déplacez la clé à la fin ; en cas de dépassement de capacité, retirez le premier élément (LRU). Vous obtenez ainsi des opérations get et put en O(1) grâce à une fonctionnalité intégrée qui repose en interne sur une liste doublement chaînée et une table de hachage.
from collections import OrderedDict
class LRUCache:
def __init__(self, capacity):
self.capacity = capacity
self.cache = OrderedDict()
def get(self, key):
if key not in self.cache:
return -1
self.cache.move_to_end(key) # mark as recently used
return self.cache[key]
def put(self, key, value):
if key in self.cache:
self.cache.move_to_end(key)
self.cache[key] = value
if len(self.cache) > self.capacity:
self.cache.popitem(last=False) # evict LRU (first item)
cache = LRUCache(2)
cache.put(1, 1); cache.put(2, 2)
print(cache.get(1)) # 1 (and 1 becomes most recently used)
cache.put(3, 3) # evict key 2 (LRU)
print(cache.get(2)) # -1
cache.put(4, 4) # evict key 1 (LRU)
print(cache.get(1)) # -1
print(cache.get(3)) # 3
print(cache.get(4)) # 4Cache LRU à partir de zéro : liste doublement chaînée + HashMap
L’implémentation construite à partir de zéro utilise une liste doublement chaînée (pour permettre la suppression d’un nœud en O(1)) et une table de hachage (pour rechercher un nœud par sa clé en O(1)). La liste conserve l’ordre du LRU (head.next) au MRU (tail.prev). Les sentinelles factices de tête et de queue éliminent les cas particuliers lors de l’insertion et de la suppression aux extrémités.
class DNode:
def __init__(self, key=0, val=0):
self.key = key
self.val = val
self.prev = None
self.next = None
class LRUCacheDLL:
def __init__(self, capacity):
self.cap = capacity
self.map = {} # key -> DNode
self.head = DNode() # dummy LRU end
self.tail = DNode() # dummy MRU end
self.head.next = self.tail
self.tail.prev = self.head
def _remove(self, node):
node.prev.next = node.next
node.next.prev = node.prev
def _add_to_tail(self, node):
node.prev = self.tail.prev
node.next = self.tail
self.tail.prev.next = node
self.tail.prev = node
def get(self, key):
if key not in self.map:
return -1
node = self.map[key]
self._remove(node)
self._add_to_tail(node)
return node.val
def put(self, key, val):
if key in self.map:
self._remove(self.map[key])
node = DNode(key, val)
self._add_to_tail(node)
self.map[key] = node
if len(self.map) > self.cap:
lru = self.head.next
self._remove(lru)
del self.map[lru.key]
cache = LRUCacheDLL(2)
cache.put(1,1); cache.put(2,2)
print(cache.get(1)) # 1
cache.put(3,3)
print(cache.get(2)) # -1 (evicted)Pourquoi une liste doublement chaînée pour LRU ?
Une liste simplement chaînée ne peut pas supprimer un nœud arbitraire en O(1) sans connaître son prédécesseur. Une liste doublement chaînée stocke les deux pointeurs prev et next, ce qui permet une suppression en O(1) lorsque la référence du nœud est connue. La table de hachage fournit un accès au nœud par sa clé en O(1). Ensemble : get(key) prend O(1) pour trouver le nœud et O(1) pour le déplacer vers la queue ; put(key) prend O(1) pour ajouter un élément et O(1) pour supprimer le nœud LRU de la tête.
# Why not a singly linked list?
# To remove a node you need its predecessor
# With SLL: must traverse from head to find predecessor => O(n)
# With DLL: node.prev IS the predecessor => O(1) removal
print('SLL removal: O(n) — must find predecessor by traversal')
print('DLL removal: O(1) — node.prev is immediately available')
print('Hash map lookup: O(1) — get DNode reference by key')
print('Combined LRU get/put: O(1) average')Cache LFU (élément utilisé le moins fréquemment)
Une variante plus difficile est le cache LFU (LeetCode 460), dans lequel l’élément ayant le plus petit nombre d’accès est expulsé. En cas d’égalité, la récence départage les éléments (le moins récemment utilisé parmi ceux qui sont les moins fréquents). L’implémentation nécessite trois structures de données : une table clé-valeur, une table clé-fréquence et une table fréquence-vers-OrderedDict (pour conserver l’ordre d’insertion au sein de chaque groupe de fréquences). Les opérations get et put de LFU sont en O(1) amorti.
from collections import defaultdict, OrderedDict
class LFUCache:
def __init__(self, capacity):
self.cap = capacity
self.min_f = 0
self.kv = {} # key -> val
self.kf = {} # key -> freq
self.fk = defaultdict(OrderedDict) # freq -> {key: None}
def _touch(self, key):
f = self.kf[key]
self.kf[key] = f + 1
del self.fk[f][key]
if not self.fk[f] and f == self.min_f:
self.min_f += 1
self.fk[f+1][key] = None
def get(self, key):
if key not in self.kv:
return -1
self._touch(key)
return self.kv[key]
def put(self, key, val):
if self.cap == 0: return
if key in self.kv:
self.kv[key] = val
self._touch(key)
else:
if len(self.kv) == self.cap:
lfu_key, _ = self.fk[self.min_f].popitem(last=False)
del self.kv[lfu_key]; del self.kf[lfu_key]
self.kv[key] = val; self.kf[key] = 1
self.fk[1][key] = None; self.min_f = 1Schémas de conception : table de hachage + liste chaînée
Le cache LRU illustre un schéma de conception puissant : combiner une table de hachage pour rechercher une clé en O(1) avec une liste chaînée pour effectuer des opérations ordonnées en O(1). Ce schéma apparaît dans plusieurs problèmes de conception posés en entretien : cache LRU, cache LFU, listes à raccourcis et certaines variantes de files. Chaque fois qu’un problème exige à la fois une recherche en O(1) et des opérations fondées sur l’ordre en O(1), envisagez cette combinaison.
Lors des entretiens, énoncer explicitement ce schéma démontre une capacité de raisonnement à l’échelle du système et une bonne connaissance des combinaisons classiques de structures de données.
Séquence consécutive dans une matrice
Il s’agit d’une extension de l’idée de séquence consécutive en deux dimensions : étant donné une matrice d’entiers, trouvez la longueur de la plus longue séquence consécutive pouvant être suivie (chaque étape se déplace vers une cellule adjacente). Cette approche combine BFS/DFS avec l’approche par ensemble pour les séquences consécutives. Stockez la position de chaque valeur, puis, pour chaque valeur de départ, vérifiez si value+1 existe comme voisin.
# Simpler: find longest consecutive values in a 2D matrix (no adjacency)
def longestConsecutiveMatrix(matrix):
all_vals = set()
for row in matrix:
for v in row:
all_vals.add(v)
best = 0
for v in all_vals:
if v - 1 not in all_vals: # start of sequence
length = 0
while v in all_vals:
v += 1
length += 1
best = max(best, length)
return best
m = [[1, 5, 3], [4, 6, 2], [8, 7, 9]]
print(longestConsecutiveMatrix(m)) # 9 (1..9 all present)Résumé d’entretien : la puissance des ensembles et de HashMap
Ces deux problèmes ont un thème commun : transformer des problèmes en O(n log n) ou O(n²) en problèmes en O(n) grâce à la structure de hachage appropriée. La plus longue séquence consécutive utilise un ensemble pour répondre à la question « le prédécesseur est-il présent ? » en O(1). Le cache LRU utilise une table de hachage pour trouver instantanément le nœud et une liste doublement chaînée pour mettre à jour l’ordre en O(1). Les deux remplacent un parcours lent par un test d’appartenance ou une recherche en O(1).
Lorsqu’un recruteur vous demande « pouvez-vous faire mieux que O(n log n) ? », la réponse est presque toujours : « utilisez une table de hachage ou un ensemble de hachage pour éviter le tri ».
Vérification rapide
Évaluez votre compréhension des concepts de Structures de données et 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 plus longue séquence consécutive s’exécute en O(n) grâce à un ensemble permettant des tests d’appartenance en O(1) et au fait de ne commencer le comptage qu’au début des séquences, le cache LRU atteint O(1) pour get et put avec un OrderedDict (ou avec une table de hachage et une liste doublement chaînée construite à partir de zéro), et le schéma table de hachage + liste chaînée est un composant réutilisable pour les structures de données en O(1) sensibles à l’ordre. Ensuite, nous reviendrons à la récursivité avec le cadre du cas de base, de la confiance et de la construction.
Apprends Python 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
- 30
- Leçons
- 120
Questions Fréquemment Posées
La leçon « Plus longue séquence consécutive et cache LRU » est-elle gratuite ?
Oui — le texte complet de « Plus longue séquence consécutive et cache LRU » 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 DSA Interview Prep, passe à CoddyKit PRO. Le cours DSA Interview Prep comprend 4 leçons au total.
Qu'est-ce que j'apprendrai dans « Plus longue séquence consécutive et cache LRU » ?
Résolvez longest-consecutive-sequence en O(n) à l’aide d’un ensemble, puis concevez un cache LRU avec un OrderedDict. Tu pratiques DSA 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 DSA Interview Prep ?
Aucune expérience préalable n'est requise. DSA 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 « Plus longue séquence consécutive et cache LRU » ?
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 DSA Interview Prep ?
Oui. Chaque leçon DSA 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
- Fonction de hachage : fonctionnement interne et gestion des collisions
- Two-Sum et ses nombreuses variantes
- Comptage des fréquences et regroupement
- Plus longue séquence consécutive et cache LRU