Exercices corrigés sur les tableaux - Niveau avancé
Cette section contient une série d'exercices avancés sur la manipulation des tableaux, avec différents niveaux de difficulté. Chaque exercice est présenté avec son énoncé, une solution détaillée et des exemples d'entrée/sortie.
Exercice 1 — Mise à jour circulaire
Remplacer chaque élément par la somme des deux suivants (circulaire)
Étant donné un tableau tab[] de taille n, la tâche consiste à remplacer chaque élément du tableau par la somme des deux éléments suivants consécutifs de manière circulaire, à savoir :
- tab[0] = tab[1] + tab[2]
- tab[1] = tab[2] + tab[3]
- ...
- tab[n-1] = tab[0] + tab[1]
Le défi principal est que les premières valeurs sont écrasées pendant la mise à jour. Il faut donc sauvegarder les premier et deuxième éléments avant de commencer les modifications.
- Stocker les premier et deuxième éléments du tableau dans les variables
firstetsecond. - Pour chaque élément sauf le dernier et l'avant-dernier : mettre à jour
tab[i] = tab[i+1] + tab[i+2]. - Mettre à jour l'avant-dernier élément :
tab[n-2] = tab[n-1] + first. - Mettre à jour le dernier élément :
tab[n-1] = first + second.
Si n < 3, la transformation n'est pas possible (car il faut au moins 3 éléments pour avoir deux suivants).
Algorithme mise_a_jour_circulaire(tab, n):
si n < 3 : retourner
first ← tab[0]
second ← tab[1]
pour i de 0 à n-3 :
tab[i] = tab[i+1] + tab[i+2]
tab[n-2] = tab[n-1] + first
tab[n-1] = first + second
retourner tabdef mise_a_jour_circulaire(tab):
"""
Remplace chaque élément par la somme des deux suivants (circulaire)
Complexité : O(n) temps, O(1) espace
"""
n = len(tab)
# Tableau invalide
if n < 3:
print("Tableau trop petit (n < 3)")
return tab
# Sauvegarder les premières valeurs
first = tab[0]
second = tab[1]
# Mettre à jour les éléments jusqu'à l'avant-avant-dernier
for i in range(n - 2):
tab[i] = tab[i + 1] + tab[i + 2]
# Mettre à jour l'avant-dernier élément
tab[n - 2] = tab[n - 1] + first
# Mettre à jour le dernier élément
tab[n - 1] = first + second
return tab
# Tests
T1 = [3, 4, 2, 1, 6]
T2 = [5, 2, 1, 3, 8]
print(f"Original: {T1}")
print(f"Modifié : {mise_a_jour_circulaire(T1)}")
print()
print(f"Original: {T2}")
print(f"Modifié : {mise_a_jour_circulaire(T2)}")Original: [3, 4, 2, 1, 6] Modifié : [6, 3, 7, 9, 7] Original: [5, 2, 1, 3, 8] Modifié : [3, 4, 11, 13, 7]
tab = [3, 4, 2, 1, 6]
[6, 3, 7, 9, 7]
- tab[0] = 4 + 2 = 6
- tab[1] = 2 + 1 = 3
- tab[2] = 1 + 6 = 7
- tab[3] = 6 + 3 (first) = 9
- tab[4] = 3 + 4 (first+second) = 7
Exercice 2 — Achat de bonbons avec contraintes
Maximiser l'achat de bonbons sous contraintes
Soit un tableau tab[] de taille n où tab[i] est la quantité de bonbons de type i. Vous avez une somme illimitée d'argent. La tâche consiste à acheter le plus grand nombre possible de bonbons répondant aux conditions suivantes :
Si vous achetez x(i) bonbons de type i (0 ≤ x(i) ≤ tab[i]), alors pour tout j (1 ≤ j ≤ i), au moins une des conditions suivantes doit être respectée :
- x(j) < x(i) (vous avez acheté moins de bonbons de type j que de type i)
- x(j) = 0 (vous avez acheté 0 bonbons de type j)
Nous pouvons utiliser une approche gloutonne et commencer à la fin du tableau.
Si nous prenons x bonbons du type i+1, nous ne pouvons prendre que min(tab[i], x-1) bonbons du type i.
Si cette valeur est négative, nous ne pouvons pas acheter de bonbons du type actuel.
Algorithme max_bonbons(tab, n):
total ← 0
precedent ← infini # on peut prendre autant qu'on veut du dernier type
pour i de n-1 à 0:
# on prend au maximum tab[i] mais aussi au maximum (precedent - 1)
pris ← min(tab[i], precedent - 1) si precedent > 0 sinon 0
# on ne peut pas prendre de quantités négatives
si pris < 0:
pris ← 0
total ← total + pris
precedent ← pris
retourner totaldef max_bonbons(tab):
"""
Calcule le nombre maximum de bonbons qu'on peut acheter
sous les contraintes données.
Complexité : O(n) temps, O(1) espace
"""
n = len(tab)
total = 0
precedent = float('inf') # on peut prendre autant qu'on veut du dernier type
# Parcourir de la fin vers le début
for i in range(n - 1, -1, -1):
# On prend au maximum tab[i] mais aussi au maximum (precedent - 1)
if precedent > 0:
pris = min(tab[i], precedent - 1)
else:
pris = 0
# On ne peut pas prendre de quantités négatives
if pris < 0:
pris = 0
total += pris
precedent = pris
return total
# Tests
T1 = [1, 2, 1, 3, 6]
T2 = [1, 1, 1, 1]
print(f"Tableau {T1} → Nombre max de bonbons: {max_bonbons(T1)}")
print(f"Tableau {T2} → Nombre max de bonbons: {max_bonbons(T2)}")Tableau [1, 2, 1, 3, 6] → Nombre max de bonbons: 10 Tableau [1, 1, 1, 1] → Nombre max de bonbons: 1
Avec [1,1,1,1], on ne peut acheter qu'un seul bonbon car :
- Pour le type 4 (dernier), on prend 1 bonbon
- Pour le type 3, on ne peut prendre que min(1, 0) = 0 (car il faut moins que le type 4)
- Pour les types 2 et 1, on ne peut rien prendre car on aurait besoin de moins que le type précédent qui est 0
Total = 1
tab = [1, 2, 1, 3, 6]
10
Exercice 3 — Différence absolue des sommes positives et négatives
Remplacer par la différence des sommes à droite
Étant donné un tableau d'éléments positifs et négatifs. La tâche consiste à remplacer chaque i-ème élément du tableau par la différence absolue des sommes absolues des éléments positifs et négatifs dans l'intervalle i+1 à N.
C'est-à-dire : trouver la somme absolue de tous les éléments positifs et la somme absolue de tous les éléments négatifs dans la plage i+1 à N. Ensuite, trouver la différence absolue entre ces deux sommes et remplacer le i-ème élément par cette valeur.
Solution naïve — O(n²)
L'approche naïve consiste à exécuter deux boucles et pour tous les i-èmes éléments, calculer la valeur absolue de la somme de tous les éléments positifs et négatifs dont l'indice est compris entre i+1 et N. Trouver la différence absolue des deux sommes et remplacer avec le i-ème élément.
Complexité : O(n²)
def remplacer_par_diff_naive(tab):
"""
Version naïve - O(n²)
"""
n = len(tab)
for i in range(n):
pos_s = 0
neg_s = 0
# calculer la somme absolue des valeurs positives et négatives
# de i+1 à n-1
for j in range(i + 1, n):
if tab[j] > 0:
pos_s += tab[j]
else:
neg_s += tab[j]
diff = abs(pos_s) - abs(neg_s)
# remplacer le ième élément par la différence
tab[i] = abs(diff)
return tab
T = [-3, -4, -2, 5, 1, -2]
print(f"Original: {T}")
print(f"Naïve : {remplacer_par_diff_naive(T)}")Solution efficace — O(n)
On peut optimiser en parcourant le tableau de la fin vers le début, en maintenant les sommes cumulées des positifs et négatifs.
- Initialiser les sommes positives et négatives à 0.
- Parcourir du dernier élément au premier :
- Calculer diff = abs(pos_s) - abs(neg_s)
- Si l'élément courant est positif, l'ajouter à pos_s, sinon à neg_s
- Remplacer l'élément courant par abs(diff)
Algorithme remplacer_par_diff_efficace(tab, n):
pos_s ← 0
neg_s ← 0
pour i de n-1 à 0:
diff = |pos_s| - |neg_s|
si tab[i] > 0:
pos_s = pos_s + tab[i]
sinon:
neg_s = neg_s + tab[i]
tab[i] = |diff|
retourner tabComplexité : O(n)
def remplacer_par_diff_efficace(tab):
"""
Version efficace - O(n)
"""
n = len(tab)
pos_s = 0
neg_s = 0
# Parcourir de la fin vers le début
for i in range(n - 1, -1, -1):
diff = abs(pos_s) - abs(neg_s)
# Si l'élément courant est positif, l'ajouter à pos_s
if tab[i] > 0:
pos_s = pos_s + tab[i]
# Sinon l'ajouter à neg_s
else:
neg_s = neg_s + tab[i]
# Remplacer l'élément par la différence absolue
tab[i] = abs(diff)
return tab
# Tests
T1 = [1, -1, 2, 3, -2]
T2 = [-3, -4, -2, 5, 1, -2]
print(f"Original 1: [1, -1, 2, 3, -2]")
print(f"Résultat 1: {remplacer_par_diff_efficace([1, -1, 2, 3, -2])}")
print()
print(f"Original 2: [-3, -4, -2, 5, 1, -2]")
print(f"Résultat 2: {remplacer_par_diff_efficace([-3, -4, -2, 5, 1, -2])}")Original 1: [1, -1, 2, 3, -2] Résultat 1: [2, 3, 1, 2, 0] Original 2: [-3, -4, -2, 5, 1, -2] Résultat 2: [2, 2, 4, 1, 2, 0]
tab = [1, -1, 2, 3, -2]
[2, 3, 1, 2, 0]
tab = [-3, -4, -2, 5, 1, -2]
[2, 2, 4, 1, 2, 0]
- 1 ≤ n ≤ 10⁵
- Les éléments peuvent être positifs ou négatifs
Récapitulatif des complexités
| Exercice | Approche naïve | Approche optimisée |
|---|---|---|
| Exercice 1 | O(n) | O(n) (direct) |
| Exercice 2 | — | O(n) (glouton) |
| Exercice 3 | O(n²) | O(n) (parcours inverse) |
- Pour les mises à jour circulaires, il faut sauvegarder les premières valeurs avant modification.
- Les problèmes avec contraintes de décroissance se résolvent souvent par une approche gloutonne depuis la fin.
- Pour les calculs de sommes sur des suffixes, parcourir de droite à gauche permet d'obtenir une complexité O(n).
- La différence entre solutions naïves et optimisées peut être considérable (O(n²) vs O(n)).
Discussion (0)
Soyez le premier à laisser un commentaire !
Laisser un commentaire
Votre commentaire sera visible après modération.