{
  "chapter": {
    "id": "complexite",
    "level": "premiere",
    "theme": "Algorithmique",
    "title": "Complexité algorithmique",
    "description": "Notion de complexité asymptotique, notation O, classes\nclassiques (constante, logarithmique, linéaire, quasi\nlinéaire, quadratique, exponentielle), calcul à partir\ndu code, comparaison expérimentale, échelle de\ncroissance.",
    "parties": [
      {
        "id": "notion",
        "title": "Notion de coût et taille d'entrée"
      },
      {
        "id": "classes",
        "title": "Classes de complexité"
      },
      {
        "id": "analyse",
        "title": "Analyser un programme"
      }
    ],
    "prerequisites": [],
    "references": []
  },
  "questions": [
    {
      "id": "q01",
      "difficulty": 1,
      "skills": [
        "definition"
      ],
      "title": "Définition de la complexité",
      "partie": "notion",
      "statement": "Que mesure la **complexité algorithmique** ?",
      "options": [
        {
          "text": "Le nombre de lignes de code source nécessaires pour écrire l'algorithme",
          "correct": false,
          "feedback": "Erreur : la complexité algorithmique mesure\nl'évolution des **ressources** (temps,\nmémoire), pas la longueur du code.\n"
        },
        {
          "text": "La quantité de ressources consommée selon la taille de l'entrée",
          "correct": true,
          "feedback": "Bonne réponse : on étudie comment le coût\n(temps de calcul, mémoire) évolue lorsque la\ntaille n de l'entrée grandit.\n"
        },
        {
          "text": "Le temps d'exécution en secondes, mesuré sur une machine donnée",
          "correct": false,
          "feedback": "Erreur : la complexité ne dépend ni de la\nmachine ni du langage. Elle compte des\nopérations élémentaires en fonction de la\ntaille n, pas des secondes sur un ordinateur\nparticulier.\n"
        },
        {
          "text": "La difficulté qu'a un lecteur humain à comprendre le code source",
          "correct": false,
          "feedback": "C'est la « complexité cognitive », notion\ndifférente, non quantifiée formellement en\nNSI.\n"
        }
      ],
      "explanation": "Deux types de complexité : **temporelle** (nombre\nd'opérations) et **spatiale** (mémoire utilisée).\nEn NSI Première, on s'intéresse surtout à la\ncomplexité temporelle."
    },
    {
      "id": "q02",
      "difficulty": 1,
      "skills": [
        "notation-O"
      ],
      "title": "Notation O",
      "partie": "classes",
      "statement": "Que signifie l'écriture `O(n)` (« grand O de n ») ?",
      "options": [
        {
          "text": "L'algorithme effectue exactement `n` opérations",
          "correct": false,
          "feedback": "Erreur : `O(n)` est une borne **asymptotique**,\npas un compte exact. L'algorithme peut faire\n`2n + 5` opérations, on note `O(n)`.\n"
        },
        {
          "text": "Le nombre d'opérations croît proportionnellement à n",
          "correct": true,
          "feedback": "Bonne réponse : O(n) signifie « linéaire ».\nDoubler n double approximativement le temps.\nLes constantes et les termes d'ordre\ninférieur sont ignorés.\n"
        },
        {
          "text": "L'algorithme utilise exactement n octets de mémoire vive",
          "correct": false,
          "feedback": "O(n) peut s'appliquer à la mémoire, mais ce\nn'est pas une définition générale.\n"
        },
        {
          "text": "L'algorithme se termine en moins de n secondes",
          "correct": false,
          "feedback": "Erreur : O ne mesure pas un temps absolu mais\nune **proportion**.\n"
        }
      ],
      "explanation": "O(n) caractérise une famille de fonctions qui\ncroissent au plus comme c × n pour une constante\nc. C'est une notation d'ordre de grandeur."
    },
    {
      "id": "q03",
      "difficulty": 1,
      "skills": [
        "classes"
      ],
      "title": "Classes classiques",
      "partie": "classes",
      "statement": "Parmi les complexités suivantes, laquelle est la\n**plus rapide** asymptotiquement ?",
      "options": [
        {
          "text": "O(n²)",
          "correct": false,
          "feedback": "Erreur : quadratique, l'une des plus lentes\ndans cette liste.\n"
        },
        {
          "text": "O(n log n)",
          "correct": false,
          "feedback": "Bonne, mais pas la plus rapide.\n"
        },
        {
          "text": "O(log n)",
          "correct": true,
          "feedback": "Bonne réponse : logarithmique. Pour n = 10⁶,\nlog₂(n) ≈ 20. C'est la complexité de la\nrecherche dichotomique.\n"
        },
        {
          "text": "O(n)",
          "correct": false,
          "feedback": "Linéaire : plus rapide que quadratique mais\nplus lente que logarithmique.\n"
        }
      ],
      "explanation": "Hiérarchie classique (du plus rapide au plus\nlent) : O(1) < O(log n) < O(n) < O(n log n) <\nO(n²) < O(n³) < O(2ⁿ) < O(n!)."
    },
    {
      "id": "q04",
      "difficulty": 1,
      "skills": [
        "linear"
      ],
      "title": "Boucle simple",
      "partie": "classes",
      "statement": "Quelle est la complexité de cette fonction en\nfonction de `n = len(liste)` ?\n```python\ndef somme(liste):\n    total = 0\n    for x in liste:\n        total += x\n    return total\n```",
      "options": [
        {
          "text": "O(log n)",
          "correct": false,
          "feedback": "Erreur : aucun mécanisme dichotomique ici.\n"
        },
        {
          "text": "O(1)",
          "correct": false,
          "feedback": "Erreur : la boucle dépend de la taille de\nla liste.\n"
        },
        {
          "text": "O(n)",
          "correct": true,
          "feedback": "Bonne réponse : une boucle simple sur n\néléments avec une opération constante par\nitération donne O(n).\n"
        },
        {
          "text": "O(n²)",
          "correct": false,
          "feedback": "Erreur : pas de double boucle.\n"
        }
      ],
      "explanation": "Règle pratique : une boucle imbriquée 0 fois\n→ O(1) ; 1 fois → O(n) ; 2 fois → O(n²) ; etc."
    },
    {
      "id": "q05",
      "difficulty": 1,
      "skills": [
        "quadratic"
      ],
      "title": "Double boucle",
      "partie": "classes",
      "statement": "Quelle est la complexité de cette fonction ?\n```python\ndef doublons(liste):\n    n = len(liste)\n    for i in range(n):\n        for j in range(n):\n            if i != j and liste[i] == liste[j]:\n                return True\n    return False\n```",
      "options": [
        {
          "text": "O(n)",
          "correct": false,
          "feedback": "Erreur : le `for j in range(n)` imbriqué\najoute un facteur n.\n"
        },
        {
          "text": "O(1)",
          "correct": false,
          "feedback": "Erreur : double boucle, donc dépend de n.\n"
        },
        {
          "text": "O(n!)",
          "correct": false,
          "feedback": "Erreur : factorielle, c'est la complexité\nd'algorithmes énumérant toutes les\npermutations.\n"
        },
        {
          "text": "O(n²)",
          "correct": true,
          "feedback": "Bonne réponse : deux boucles imbriquées sur\nn éléments donnent n × n = n² opérations\nau pire.\n"
        }
      ],
      "explanation": "Deux boucles imbriquées donnent O(n²). Astuce :\non peut faire mieux ici en O(n) avec un seul\nparcours qui note dans un dictionnaire les\nvaleurs déjà vues, car le test `x in vues` sur\nles clés d'un dictionnaire est en O(1) en\nmoyenne."
    },
    {
      "id": "q06",
      "difficulty": 1,
      "skills": [
        "constante"
      ],
      "title": "Constante",
      "partie": "classes",
      "statement": "Quelle est la complexité de l'accès `liste[i]`\nsur une **liste Python** ?",
      "options": [
        {
          "text": "O(n²)",
          "correct": false,
          "feedback": "Erreur : un accès par indice est trivial.\n"
        },
        {
          "text": "O(log n)",
          "correct": false,
          "feedback": "Erreur : aucune dichotomie nécessaire,\naccès direct.\n"
        },
        {
          "text": "O(n)",
          "correct": false,
          "feedback": "Erreur : pour une `list` Python, l'accès par\nindice est immédiat, on n'a pas besoin de\nparcourir la liste.\n"
        },
        {
          "text": "O(1)",
          "correct": true,
          "feedback": "Bonne réponse : les listes Python sont des\ntableaux, l'accès par indice est en temps\nconstant indépendamment de la taille.\n"
        }
      ],
      "explanation": "Distinguer : `liste[i]` est en O(1) ;\n`cible in liste` est en O(n) ; `liste.append(x)`\nest en O(1) en moyenne ; insérer un élément en\ntête de liste, en revanche, oblige à décaler\ntous les autres et coûte O(n)."
    },
    {
      "id": "q07",
      "difficulty": 1,
      "skills": [
        "taille-entree"
      ],
      "title": "Taille de l'entrée",
      "partie": "notion",
      "statement": "Pour une liste, qu'est-ce que la « taille de\nl'entrée » `n` ?",
      "options": [
        {
          "text": "Le numéro de ligne où la liste est définie",
          "correct": false,
          "feedback": "Le numéro de ligne du\ncode source ne joue\naucun rôle dans le coût\nd'exécution. La taille\nd'entrée caractérise le\nvolume de données traité,\nici le nombre d'éléments.\n"
        },
        {
          "text": "La valeur du plus grand élément",
          "correct": false,
          "feedback": "Erreur : la complexité ne dépend\ngénéralement pas des **valeurs**, mais de\nla **structure**.\n"
        },
        {
          "text": "La quantité de mémoire qu'elle occupe en octets",
          "correct": false,
          "feedback": "C'est lié, mais pas la définition standard.\n"
        },
        {
          "text": "Le nombre d'éléments de la liste (sa longueur)",
          "correct": true,
          "feedback": "Bonne réponse : la taille pertinente pour\nla complexité d'une liste est sa longueur.\n"
        }
      ],
      "explanation": "Selon le contexte, la « taille » peut être\ndifférente : nombre de chiffres pour un entier,\nnombre de pixels pour une image, nombre de lignes\npour un fichier, etc."
    },
    {
      "id": "q08",
      "difficulty": 1,
      "skills": [
        "pire-moyen-meilleur"
      ],
      "title": "Pire / moyen / meilleur cas",
      "partie": "notion",
      "statement": "Pourquoi distingue-t-on **pire cas, cas moyen, meilleur cas** dans l'analyse de complexité ?",
      "options": [
        {
          "text": "Pour pouvoir annoncer la complexité la plus avantageuse",
          "correct": false,
          "feedback": "Erreur : on ne choisit pas le cas qui\narrange. En pratique, on retient le\n**pire cas**, car il fournit une\ngarantie valable pour toutes les\nentrées possibles.\n"
        },
        {
          "text": "Parce que les langages de programmation diffèrent",
          "correct": false,
          "feedback": "Sans rapport : la complexité est\nintrinsèque à l'algorithme.\n"
        },
        {
          "text": "Parce que la complexité peut dépendre du contenu, pas seulement de la taille",
          "correct": true,
          "feedback": "Bonne réponse : pour le tri par insertion,\nune liste déjà triée donne O(n), une liste\nrenversée donne O(n²). Le pire cas est le\nplus important pour les garanties.\n"
        },
        {
          "text": "Parce que le résultat renvoyé change selon les données",
          "correct": false,
          "feedback": "Erreur : confusion entre coût et\ncorrection. Un algorithme correct\nrenvoie le bon résultat dans tous\nles cas ; ce qui varie d'un cas à\nl'autre, c'est le **nombre\nd'opérations** effectuées, pas le\nrésultat.\n"
        }
      ],
      "explanation": "En général, on retient le **pire cas** comme\nréférence : il garantit qu'au-delà, le coût ne\ndépassera pas. Le cas moyen est utile pour\nl'analyse fine en pratique."
    },
    {
      "id": "q09",
      "difficulty": 1,
      "skills": [
        "comparaison-tailles"
      ],
      "title": "Échelle de croissance",
      "partie": "notion",
      "statement": "Pour n = 1000, qui est le plus grand ?",
      "options": [
        {
          "text": "n",
          "correct": false,
          "feedback": "Erreur : n = 1000 = 10³, valeur\nintermédiaire parmi les quatre.\n"
        },
        {
          "text": "2ⁿ",
          "correct": true,
          "feedback": "Bonne réponse : 2¹⁰⁰⁰ ≈ 10³⁰¹, un nombre de\nplus de 300 chiffres, astronomiquement plus\ngrand que 10⁶ ou 10³. C'est pourquoi on\névite à tout prix les algorithmes\nexponentiels sur de grandes entrées.\n"
        },
        {
          "text": "n²",
          "correct": false,
          "feedback": "Erreur : n² = 10⁶, c'est grand mais très\nloin derrière l'exponentielle.\n"
        },
        {
          "text": "log₂(n)",
          "correct": false,
          "feedback": "Erreur : log₂(1000) ≈ 10, c'est la valeur\nla plus petite des quatre.\n"
        }
      ],
      "explanation": "Échelle pour n = 1000 : log n = 10, n = 10³,\nn² = 10⁶, 2ⁿ ≈ 10³⁰¹. Différence colossale."
    },
    {
      "id": "q10",
      "difficulty": 1,
      "skills": [
        "exemple-recherche"
      ],
      "title": "Recherche",
      "partie": "analyse",
      "statement": "Quelle est la complexité de la **recherche dichotomique** dans une liste triée ?",
      "options": [
        {
          "text": "O(n)",
          "correct": false,
          "feedback": "Erreur : c'est la recherche **linéaire**,\npas dichotomique.\n"
        },
        {
          "text": "O(log n)",
          "correct": true,
          "feedback": "Bonne réponse : à chaque étape, on divise\nla liste en deux. C'est la complexité de\nréférence pour les algorithmes de\ndichotomie.\n"
        },
        {
          "text": "O(n²)",
          "correct": false,
          "feedback": "Erreur : aucun double parcours.\n"
        },
        {
          "text": "O(1)",
          "correct": false,
          "feedback": "Erreur : on doit comparer plusieurs\néléments dans le pire cas.\n"
        }
      ],
      "explanation": "Hiérarchie classique : O(log n) (dichotomie)\n<< O(n) (parcours simple) << O(n²) (double\nparcours) << O(2ⁿ) (énumération exponentielle)."
    },
    {
      "id": "q11",
      "difficulty": 2,
      "skills": [
        "calcul-imbrication"
      ],
      "title": "Boucles imbriquées",
      "partie": "analyse",
      "statement": "Quelle est la complexité de :\n```python\nfor i in range(n):\n    for j in range(n):\n        for k in range(n):\n            print(i, j, k)\n```",
      "options": [
        {
          "text": "O(n)",
          "correct": false,
          "feedback": "Erreur : trois boucles imbriquées.\n"
        },
        {
          "text": "O(n²)",
          "correct": false,
          "feedback": "Erreur : c'est deux boucles imbriquées,\npas trois.\n"
        },
        {
          "text": "O(3n)",
          "correct": false,
          "feedback": "Erreur : ce serait trois boucles **non imbriquées**, soit O(n).\n"
        },
        {
          "text": "O(n³)",
          "correct": true,
          "feedback": "Bonne réponse : trois boucles imbriquées\nsur n donnent n × n × n = n³ opérations.\n"
        }
      ],
      "explanation": "Règle simple : pour des boucles imbriquées\nsur n, la complexité est n^(profondeur)."
    },
    {
      "id": "q12",
      "difficulty": 2,
      "skills": [
        "boucles-non-imbriquees"
      ],
      "title": "Deux boucles séparées",
      "partie": "analyse",
      "statement": "Quelle est la complexité de :\n```python\nfor i in range(n):\n    print(i)\nfor j in range(n):\n    print(j)\n```",
      "options": [
        {
          "text": "O(2)",
          "correct": false,
          "feedback": "Erreur : le coût dépend de n.\n"
        },
        {
          "text": "O(n²)",
          "correct": false,
          "feedback": "Erreur : les boucles ne sont **pas imbriquées**, on les additionne, on ne les\nmultiplie pas.\n"
        },
        {
          "text": "O(n)",
          "correct": true,
          "feedback": "Bonne réponse : n + n = 2n opérations, et 2n\nest un O(n), les constantes sont absorbées.\nOn n'écrit jamais O(2n), seulement O(n).\n"
        },
        {
          "text": "O(n log n)",
          "correct": false,
          "feedback": "Erreur : aucune division du problème ni tri\nici, seulement deux parcours successifs de n\ntours. Le total vaut 2n, c'est un O(n).\n"
        }
      ],
      "explanation": "Règle : O(f) + O(g) = O(max(f, g)). La\ncomplexité d'une séquence est dominée par sa\npartie la plus coûteuse."
    },
    {
      "id": "q13",
      "difficulty": 2,
      "skills": [
        "domination"
      ],
      "title": "Termes dominants",
      "partie": "classes",
      "statement": "Quelle est la complexité de l'expression\n`T(n) = 3n² + 100n + 50` ?",
      "options": [
        {
          "text": "O(3n²)",
          "correct": false,
          "feedback": "Erreur : on n'écrit pas la constante\nmultiplicative dans la notation O.\n"
        },
        {
          "text": "O(n²)",
          "correct": true,
          "feedback": "Bonne réponse : le terme **dominant** pour\nn grand est `3n²`. Les autres termes (100n,\n50) deviennent négligeables.\n"
        },
        {
          "text": "O(50)",
          "correct": false,
          "feedback": "Erreur : 50 est une constante mais elle est\ndominée par les termes en n.\n"
        },
        {
          "text": "O(100n)",
          "correct": false,
          "feedback": "Erreur : on n'écrit pas la constante. De\nplus, c'est dominé par n².\n"
        }
      ],
      "explanation": "Règle systématique : ne garder que le terme\nle plus puissant et supprimer la constante\nmultiplicative. C'est l'essence de la notation\nasymptotique."
    },
    {
      "id": "q14",
      "difficulty": 2,
      "skills": [
        "extrapolation",
        "quadratique"
      ],
      "title": "Doublement de la taille",
      "partie": "notion",
      "statement": "Un algorithme en O(n²) traite n = 1000\néléments en 1 seconde. Combien de temps lui\nfaudra-t-il pour n = 2000 ?",
      "options": [
        {
          "text": "1 seconde (le temps ne change pas)",
          "correct": false,
          "feedback": "Erreur : ce serait O(1).\n"
        },
        {
          "text": "4 secondes (n doublé → temps quadruplé)",
          "correct": true,
          "feedback": "Bonne réponse : O(n²) signifie temps ∝ n².\nMultiplier n par 2 multiplie le temps par 4\n(= 2²). Doubler n multiplierait par 4, tripler par 9.\n"
        },
        {
          "text": "1000 secondes",
          "correct": false,
          "feedback": "Surestimation grossière.\n"
        },
        {
          "text": "2 secondes (n doublé → temps doublé)",
          "correct": false,
          "feedback": "Erreur : ce serait O(n).\n"
        }
      ],
      "explanation": "Compétence essentielle : extrapoler le temps\nd'exécution. Cela permet d'**anticiper** si un\nalgorithme va supporter la charge.",
      "variants": [
        {
          "statement": "Un algorithme en O(n²) traite n = 1000\néléments en 1 seconde. Combien de temps lui\nfaudra-t-il pour n = 2000 ?",
          "options": [
            {
              "text": "1 seconde (le temps ne change pas)",
              "correct": false,
              "feedback": "Erreur : ce serait O(1).\n"
            },
            {
              "text": "4 secondes (n doublé → temps quadruplé)",
              "correct": true,
              "feedback": "Bonne réponse : O(n²) signifie temps ∝ n².\nMultiplier n par 2 multiplie le temps par 4\n(= 2²). Doubler n multiplierait par 4, tripler par 9.\n"
            },
            {
              "text": "1000 secondes",
              "correct": false,
              "feedback": "Surestimation grossière.\n"
            },
            {
              "text": "2 secondes (n doublé → temps doublé)",
              "correct": false,
              "feedback": "Erreur : ce serait O(n).\n"
            }
          ],
          "explanation": "Compétence essentielle : extrapoler le temps\nd'exécution. Cela permet d'**anticiper** si un\nalgorithme va supporter la charge."
        },
        {
          "statement": "Un algorithme en O(n²) traite n = 500\néléments en 2 secondes. Combien de temps lui\nfaudra-t-il pour n = 1500 ?",
          "options": [
            {
              "text": "2 secondes (le temps ne change pas)",
              "correct": false,
              "feedback": "Erreur : ce serait O(1).\n"
            },
            {
              "text": "18 secondes (n triplé → temps multiplié par 9)",
              "correct": true,
              "feedback": "Bonne réponse : O(n²) signifie temps ∝ n².\nMultiplier n par 3 multiplie le temps par 9\n(= 3²). Doubler n multiplierait par 4, tripler par 9.\n"
            },
            {
              "text": "1000 secondes",
              "correct": false,
              "feedback": "Surestimation grossière.\n"
            },
            {
              "text": "6 secondes (n triplé → temps triplé)",
              "correct": false,
              "feedback": "Erreur : ce serait O(n).\n"
            }
          ],
          "explanation": "Compétence essentielle : extrapoler le temps\nd'exécution. Cela permet d'**anticiper** si un\nalgorithme va supporter la charge."
        },
        {
          "statement": "Un algorithme en O(n²) traite n = 2000\néléments en 1 seconde. Combien de temps lui\nfaudra-t-il pour n = 8000 ?",
          "options": [
            {
              "text": "1 seconde (le temps ne change pas)",
              "correct": false,
              "feedback": "Erreur : ce serait O(1).\n"
            },
            {
              "text": "16 secondes (n quadruplé → temps multiplié par 16)",
              "correct": true,
              "feedback": "Bonne réponse : O(n²) signifie temps ∝ n².\nMultiplier n par 4 multiplie le temps par 16\n(= 4²). Doubler n multiplierait par 4, tripler par 9.\n"
            },
            {
              "text": "2000 secondes",
              "correct": false,
              "feedback": "Surestimation grossière.\n"
            },
            {
              "text": "4 secondes (n quadruplé → temps quadruplé)",
              "correct": false,
              "feedback": "Erreur : ce serait O(n).\n"
            }
          ],
          "explanation": "Compétence essentielle : extrapoler le temps\nd'exécution. Cela permet d'**anticiper** si un\nalgorithme va supporter la charge."
        }
      ]
    },
    {
      "id": "q15",
      "difficulty": 2,
      "skills": [
        "meilleur-pire"
      ],
      "title": "Pire vs meilleur cas",
      "partie": "notion",
      "statement": "La recherche linéaire est en `O(n)` au pire et\n`O(1)` au meilleur. Que dire si l'on parle de\n« complexité » sans précision ?",
      "options": [
        {
          "text": "On parle du cas moyen",
          "correct": false,
          "feedback": "Pas la convention standard, sauf\nprécision.\n"
        },
        {
          "text": "On parle implicitement du meilleur cas",
          "correct": false,
          "feedback": "Erreur : par convention, on prend le pire\ncas si rien n'est précisé.\n"
        },
        {
          "text": "On parle du cas le plus probable",
          "correct": false,
          "feedback": "Pas une notion bien définie sans modèle\nprobabiliste.\n"
        },
        {
          "text": "On parle implicitement du pire cas",
          "correct": true,
          "feedback": "Bonne réponse : c'est la convention en NSI\net plus largement. Le pire cas garantit\nune borne supérieure.\n"
        }
      ],
      "explanation": "Le pire cas a une signification claire et\ngarantit la performance « au pire ». Le cas\nmoyen demanderait de modéliser la\ndistribution de probabilité des entrées."
    },
    {
      "id": "q16",
      "difficulty": 2,
      "skills": [
        "boucles-imbriquees",
        "matrices"
      ],
      "title": "Créer une grille n × n",
      "partie": "analyse",
      "statement": "On crée une grille de $n$ lignes et $n$ colonnes remplie de zéros :\n\n```python\ngrille = [[0 for j in range(n)] for i in range(n)]\n```\n\nQuel est le coût de cette instruction en fonction de $n$ ?",
      "options": [
        {
          "text": "$O(n)$",
          "correct": false,
          "feedback": "Erreur : l'instruction tient sur une ligne, mais elle contient deux\nboucles imbriquées. Chacune des $n$ lignes demande elle-même $n$\nopérations.\n"
        },
        {
          "text": "$O(1)$",
          "correct": false,
          "feedback": "Erreur : une seule instruction ne signifie pas un coût constant.\nCréer $n^2$ cases demande un travail qui grandit avec $n$.\n"
        },
        {
          "text": "$O(n^2)$",
          "correct": true,
          "feedback": "Bonne réponse : la compréhension externe fait $n$ tours et chacun\nconstruit une ligne de $n$ zéros par la compréhension interne.\nCela fait $n \\times n = n^2$ cases à remplir.\n"
        },
        {
          "text": "$O(2n)$",
          "correct": false,
          "feedback": "Erreur : les deux compréhensions ne se suivent pas, elles sont\nimbriquées : on multiplie leurs nombres de tours au lieu de les\nadditionner, ce qui donne $n^2$.\n"
        }
      ],
      "explanation": "Une compréhension cache une boucle. Deux compréhensions imbriquées se\ncomptent comme deux boucles imbriquées : $n \\times n$ opérations, soit\nun coût quadratique. Pour $n = 1\\,000$, cela fait déjà un million de\ncases."
    },
    {
      "id": "q17",
      "difficulty": 2,
      "skills": [
        "boucles-imbriquees",
        "fusion-tables"
      ],
      "title": "Fusionner deux tables",
      "partie": "analyse",
      "statement": "Pour fusionner une table d'élèves et une table de notes, chacune de\n$n$ lignes, on écrit :\n\n```python\ndef fusion(eleves, notes):\n    resultat = []\n    for e in eleves:\n        for x in notes:\n            if e[\"nom\"] == x[\"nom\"]:\n                resultat.append({\"nom\": e[\"nom\"], \"classe\": e[\"classe\"], \"note\": x[\"note\"]})\n    return resultat\n```\n\nQuel est le coût de `fusion` en fonction de $n$ ?",
      "options": [
        {
          "text": "$O(2n)$",
          "correct": false,
          "feedback": "Erreur : les deux tables ne sont pas parcourues l'une après\nl'autre, mais l'une dans l'autre. Les nombres de tours se\nmultiplient, d'où $n^2$.\n"
        },
        {
          "text": "$O(n^2)$",
          "correct": true,
          "feedback": "Bonne réponse : pour chacune des $n$ lignes d'élèves, la boucle\ninterne parcourt les $n$ lignes de notes. Le test d'égalité des\nnoms est donc évalué $n \\times n$ fois : $90\\,000$ fois pour deux\ntables de $300$ lignes.\n"
        },
        {
          "text": "$O(\\log n)$",
          "correct": false,
          "feedback": "Erreur : aucune moitié de table n'est éliminée ici, les boucles\nexaminent toutes les lignes. Un coût logarithmique supposerait une\nrecherche dichotomique dans une table triée.\n"
        },
        {
          "text": "$O(n)$",
          "correct": false,
          "feedback": "Erreur : même si chaque élève n'a qu'une note, la boucle interne\nne s'arrête pas quand elle la trouve. Elle parcourt toute la table\ndes notes, pour chaque élève.\n"
        }
      ],
      "explanation": "Fusionner deux tables par deux boucles imbriquées coûte $n^2$\ncomparaisons. Sur de grandes tables, ce coût devient vite\nprohibitif ; trier la table des notes par nom permettrait de\nchercher chaque élève par dichotomie."
    },
    {
      "id": "q18",
      "difficulty": 2,
      "skills": [
        "exponentielle"
      ],
      "title": "Algorithmes exponentiels",
      "partie": "classes",
      "statement": "Pourquoi évite-t-on les algorithmes en O(2ⁿ) ?",
      "options": [
        {
          "text": "Parce qu'ils occupent trop de place sur le disque dur de la machine",
          "correct": false,
          "feedback": "L'espace de stockage n'est pas en cause :\nc'est le temps de calcul qui devient\nprohibitif.\n"
        },
        {
          "text": "Parce qu'aucun problème concret ne conduit à un tel algorithme",
          "correct": false,
          "feedback": "Erreur : ils existent bel et bien, par\nexemple pour énumérer tous les sous-ensembles\nd'un ensemble de n éléments.\n"
        },
        {
          "text": "Parce que leur coût explose dès que n atteint quelques dizaines",
          "correct": true,
          "feedback": "Bonne réponse : pour n = 50, 2ⁿ ≈ 10¹⁵\nopérations, hors d'atteinte même sur un\nsuper-calculateur. La croissance\nexponentielle rend l'algorithme impraticable\ndès que n dépasse quelques dizaines.\n"
        },
        {
          "text": "Parce qu'ils résultent presque toujours d'une programmation maladroite",
          "correct": false,
          "feedback": "Erreur : un algorithme exponentiel peut être\nparfaitement écrit, c'est sa **structure**\nqui pose problème.\n"
        }
      ],
      "explanation": "Un algorithme exponentiel sur n = 100 prendrait\ndes milliards d'années même sur le meilleur\nordinateur du monde. Pour ces problèmes, on\nutilise des heuristiques ou des algorithmes\napprochés."
    },
    {
      "id": "q19",
      "difficulty": 2,
      "skills": [
        "calcul-temps"
      ],
      "title": "Estimer un temps",
      "partie": "notion",
      "statement": "Un algorithme effectue 10⁹ opérations\nélémentaires. Sur un PC moderne (environ 10⁹\nopérations / seconde), combien de temps\nattendra-t-on ?",
      "options": [
        {
          "text": "Environ une seconde",
          "correct": true,
          "feedback": "Bonne réponse : 10⁹ opérations à\nenviron 10⁹ opérations par seconde donnent\nenviron une seconde (et plutôt\nquelques secondes en Python, qui\ntourne autour de 10⁸ par seconde).\n"
        },
        {
          "text": "Plusieurs années",
          "correct": false,
          "feedback": "Erreur : à 10⁹ opérations par seconde, une\nannée représente environ 3 × 10¹⁶\nopérations. Plusieurs années correspondent\ndonc à environ 10¹⁷ opérations, très loin de 10⁹.\n"
        },
        {
          "text": "Plusieurs jours",
          "correct": false,
          "feedback": "Erreur : une journée représente environ\n10¹⁴ opérations à cette vitesse. Pour 10⁹\nopérations, le calcul donne 10⁹ / 10⁹ = 1\nseconde.\n"
        },
        {
          "text": "Quelques nanosecondes",
          "correct": false,
          "feedback": "Erreur : 10⁹ opérations à 10⁹/s = 1\nseconde, pas une nanoseconde.\n"
        }
      ],
      "explanation": "Repère mental : 10⁸ par seconde en Python (un\npeu lent), 10⁹ ou plus en C/C++. Permet\nd'anticiper si un algorithme passera ou non\nla contrainte de temps."
    },
    {
      "id": "q20",
      "difficulty": 2,
      "skills": [
        "comparaison-grand-petit"
      ],
      "title": "Petit vs grand n",
      "partie": "notion",
      "statement": "Pour n petit (n = 10), un algorithme O(n²) est-il\nforcément moins efficace qu'un algorithme O(n) ?",
      "options": [
        {
          "text": "Aucun rapport entre ces deux complexités",
          "correct": false,
          "feedback": "Erreur : ces deux coûts se comparent bien ; pour de grandes\nentrées, O(n) finit toujours par battre O(n²).\n"
        },
        {
          "text": "Oui, O(n²) est toujours moins efficace",
          "correct": false,
          "feedback": "Erreur : la notation O cache des\n**constantes** qui peuvent renverser le\nclassement sur les petites entrées.\n"
        },
        {
          "text": "Non : sur les petites entrées, les constantes peuvent dominer",
          "correct": true,
          "feedback": "Bonne réponse : un algorithme qui fait exactement 100 × n\nopérations est en O(n), un autre qui en fait n² est en O(n²).\nPour n = 10, le premier fait 1 000 opérations et le second\nseulement 100.\n"
        },
        {
          "text": "Non, car pour n = 10 les deux donnent le même nombre d'opérations",
          "correct": false,
          "feedback": "Erreur de calcul : pour n = 10, n² vaut 100 alors que n vaut 10.\nLes deux nombres d'opérations ne coïncident pas ; la vraie raison\nest que la notation O cache des constantes multiplicatives qui\npeuvent renverser le classement.\n"
        }
      ],
      "explanation": "À retenir : la notation O décrit l'évolution du coût pour de\ngrandes tailles. Pour les petits n, des constantes plus petites\npeuvent compenser un ordre de grandeur plus grand ; pour les\ngrands n, l'ordre de grandeur l'emporte."
    },
    {
      "id": "q21",
      "difficulty": 3,
      "skills": [
        "analyse-detaillee"
      ],
      "title": "Analyse fine",
      "partie": "analyse",
      "statement": "Quelle est la complexité de :\n```python\ndef f(n):\n    for i in range(n):\n        for j in range(i):\n            print(i, j)\n```",
      "options": [
        {
          "text": "O(n²)",
          "correct": true,
          "feedback": "Bonne réponse : la boucle interne fait i\nitérations. Total = 0 + 1 + ... + (n-1)\n= n(n-1)/2 ≈ n²/2. C'est O(n²).\n"
        },
        {
          "text": "O(n log n)",
          "correct": false,
          "feedback": "Erreur : aucun mécanisme dichotomique.\n"
        },
        {
          "text": "O(n)",
          "correct": false,
          "feedback": "Erreur : double boucle.\n"
        },
        {
          "text": "O(n³)",
          "correct": false,
          "feedback": "Erreur : trois boucles imbriquées\ndonneraient n³.\n"
        }
      ],
      "explanation": "Astuce : même si la boucle interne ne va pas\njusqu'à n, la somme reste en n². Cas typique\ndes tris en O(n²)."
    },
    {
      "id": "q22",
      "difficulty": 3,
      "skills": [
        "boucle-divise"
      ],
      "title": "Boucle qui divise",
      "partie": "analyse",
      "statement": "Une boucle parcourt une variable `n` en la\n**divisant par 2** à chaque tour, jusqu'à\natteindre 1 (à chaque tour, travail\nconstant). Quel est le coût total ?",
      "options": [
        {
          "text": "Quadratique",
          "correct": false,
          "feedback": "Erreur : aucun double parcours.\n"
        },
        {
          "text": "Constant",
          "correct": false,
          "feedback": "Erreur : il y a plusieurs tours.\n"
        },
        {
          "text": "Logarithmique",
          "correct": true,
          "feedback": "Bonne réponse : on passe de n à n/2 à n/4\nà ... à 1, soit log₂(n) étapes. C'est\nexactement le coût de la dichotomie.\n"
        },
        {
          "text": "Linéaire",
          "correct": false,
          "feedback": "Erreur : on divise par 2, pas par\nsoustraction de 1.\n"
        }
      ],
      "explanation": "Schéma caractéristique : à chaque itération,\nla quantité de travail restant est divisée\npar 2. Donne un coût logarithmique. C'est\nl'analyse de la recherche dichotomique."
    },
    {
      "id": "q23",
      "difficulty": 3,
      "skills": [
        "lineaire",
        "comptage",
        "liste-triee"
      ],
      "title": "Doublons dans une liste triée",
      "partie": "classes",
      "statement": "Pour savoir si une liste **triée** contient un doublon, on compare\nchaque élément à son voisin :\n```python\ndef a_doublon(t):\n    for i in range(len(t) - 1):\n        if t[i] == t[i + 1]:\n            return True\n    return False\n```\nPour une liste triée d'un million d'éléments sans doublon, combien de\nfois le test `t[i] == t[i + 1]` est-il évalué, et quel est le coût ?",
      "options": [
        {
          "text": "Environ $500\\,000\\,000\\,000$ fois, coût quadratique",
          "correct": false,
          "feedback": "Erreur : ce serait le nombre de tests d'une double boucle\ncomparant tous les couples d'éléments, nécessaire pour une liste\nnon triée. Ici il n'y a qu'une boucle, car dans une liste triée\nles doublons sont voisins.\n"
        },
        {
          "text": "$1\\,000\\,000$ fois, coût linéaire",
          "correct": false,
          "feedback": "Erreur : la boucle s'arrête à l'indice `len(t) - 2`, car le\ndernier élément n'a pas de voisin à droite. On fait donc\n$999\\,999$ tests, pas $1\\,000\\,000$ ; le coût est bien linéaire.\n"
        },
        {
          "text": "Environ $19$ fois, coût logarithmique",
          "correct": false,
          "feedback": "Erreur : le fait que la liste soit triée n'entraîne pas une\ndichotomie. Cette fonction examine les voisins un par un, sans\njamais éliminer de moitié, et fait $999\\,999$ tests.\n"
        },
        {
          "text": "$999\\,999$ fois, coût linéaire",
          "correct": true,
          "feedback": "Bonne réponse : `i` prend les valeurs de $0$ à $999\\,998$, soit\n$999\\,999$ tests, et aucun ne renvoie `True`. Comme la liste est\ntriée, deux valeurs égales sont forcément voisines : un seul\nparcours suffit, d'où un coût linéaire.\n"
        }
      ],
      "explanation": "Trier une table facilite la recherche de doublons : ils deviennent\nvoisins et un parcours linéaire suffit. Sans tri, il faudrait\ncomparer tous les couples, soit environ $n^2 / 2$ comparaisons.",
      "variants": [
        {
          "statement": "Pour savoir si une liste **triée** contient un doublon, on compare\nchaque élément à son voisin :\n```python\ndef a_doublon(t):\n    for i in range(len(t) - 1):\n        if t[i] == t[i + 1]:\n            return True\n    return False\n```\nPour une liste triée d'un million d'éléments sans doublon, combien de\nfois le test `t[i] == t[i + 1]` est-il évalué, et quel est le coût ?",
          "options": [
            {
              "text": "Environ $500\\,000\\,000\\,000$ fois, coût quadratique",
              "correct": false,
              "feedback": "Erreur : ce serait le nombre de tests d'une double boucle\ncomparant tous les couples d'éléments, nécessaire pour une liste\nnon triée. Ici il n'y a qu'une boucle, car dans une liste triée\nles doublons sont voisins.\n"
            },
            {
              "text": "$1\\,000\\,000$ fois, coût linéaire",
              "correct": false,
              "feedback": "Erreur : la boucle s'arrête à l'indice `len(t) - 2`, car le\ndernier élément n'a pas de voisin à droite. On fait donc\n$999\\,999$ tests, pas $1\\,000\\,000$ ; le coût est bien linéaire.\n"
            },
            {
              "text": "Environ $19$ fois, coût logarithmique",
              "correct": false,
              "feedback": "Erreur : le fait que la liste soit triée n'entraîne pas une\ndichotomie. Cette fonction examine les voisins un par un, sans\njamais éliminer de moitié, et fait $999\\,999$ tests.\n"
            },
            {
              "text": "$999\\,999$ fois, coût linéaire",
              "correct": true,
              "feedback": "Bonne réponse : `i` prend les valeurs de $0$ à $999\\,998$, soit\n$999\\,999$ tests, et aucun ne renvoie `True`. Comme la liste est\ntriée, deux valeurs égales sont forcément voisines : un seul\nparcours suffit, d'où un coût linéaire.\n"
            }
          ],
          "explanation": "Trier une table facilite la recherche de doublons : ils deviennent\nvoisins et un parcours linéaire suffit. Sans tri, il faudrait\ncomparer tous les couples, soit environ $n^2 / 2$ comparaisons."
        },
        {
          "statement": "Pour savoir si une liste **triée** contient un doublon, on compare\nchaque élément à son voisin :\n```python\ndef a_doublon(t):\n    for i in range(len(t) - 1):\n        if t[i] == t[i + 1]:\n            return True\n    return False\n```\nPour une liste triée de cinq mille éléments sans doublon, combien de\nfois le test `t[i] == t[i + 1]` est-il évalué, et quel est le coût ?",
          "options": [
            {
              "text": "Environ $12\\,500\\,000$ fois, coût quadratique",
              "correct": false,
              "feedback": "Erreur : ce serait le nombre de tests d'une double boucle\ncomparant tous les couples d'éléments, nécessaire pour une liste\nnon triée. Ici il n'y a qu'une boucle, car dans une liste triée\nles doublons sont voisins.\n"
            },
            {
              "text": "$5\\,000$ fois, coût linéaire",
              "correct": false,
              "feedback": "Erreur : la boucle s'arrête à l'indice `len(t) - 2`, car le\ndernier élément n'a pas de voisin à droite. On fait donc\n$4\\,999$ tests, pas $5\\,000$ ; le coût est bien linéaire.\n"
            },
            {
              "text": "Environ $12$ fois, coût logarithmique",
              "correct": false,
              "feedback": "Erreur : le fait que la liste soit triée n'entraîne pas une\ndichotomie. Cette fonction examine les voisins un par un, sans\njamais éliminer de moitié, et fait $4\\,999$ tests.\n"
            },
            {
              "text": "$4\\,999$ fois, coût linéaire",
              "correct": true,
              "feedback": "Bonne réponse : `i` prend les valeurs de $0$ à $4\\,998$, soit\n$4\\,999$ tests, et aucun ne renvoie `True`. Comme la liste est\ntriée, deux valeurs égales sont forcément voisines : un seul\nparcours suffit, d'où un coût linéaire.\n"
            }
          ],
          "explanation": "Trier une table facilite la recherche de doublons : ils deviennent\nvoisins et un parcours linéaire suffit. Sans tri, il faudrait\ncomparer tous les couples, soit environ $n^2 / 2$ comparaisons."
        },
        {
          "statement": "Pour savoir si une liste **triée** contient un doublon, on compare\nchaque élément à son voisin :\n```python\ndef a_doublon(t):\n    for i in range(len(t) - 1):\n        if t[i] == t[i + 1]:\n            return True\n    return False\n```\nPour une liste triée de cent mille éléments sans doublon, combien de\nfois le test `t[i] == t[i + 1]` est-il évalué, et quel est le coût ?",
          "options": [
            {
              "text": "Environ $5\\,000\\,000\\,000$ fois, coût quadratique",
              "correct": false,
              "feedback": "Erreur : ce serait le nombre de tests d'une double boucle\ncomparant tous les couples d'éléments, nécessaire pour une liste\nnon triée. Ici il n'y a qu'une boucle, car dans une liste triée\nles doublons sont voisins.\n"
            },
            {
              "text": "$100\\,000$ fois, coût linéaire",
              "correct": false,
              "feedback": "Erreur : la boucle s'arrête à l'indice `len(t) - 2`, car le\ndernier élément n'a pas de voisin à droite. On fait donc\n$99\\,999$ tests, pas $100\\,000$ ; le coût est bien linéaire.\n"
            },
            {
              "text": "Environ $16$ fois, coût logarithmique",
              "correct": false,
              "feedback": "Erreur : le fait que la liste soit triée n'entraîne pas une\ndichotomie. Cette fonction examine les voisins un par un, sans\njamais éliminer de moitié, et fait $99\\,999$ tests.\n"
            },
            {
              "text": "$99\\,999$ fois, coût linéaire",
              "correct": true,
              "feedback": "Bonne réponse : `i` prend les valeurs de $0$ à $99\\,998$, soit\n$99\\,999$ tests, et aucun ne renvoie `True`. Comme la liste est\ntriée, deux valeurs égales sont forcément voisines : un seul\nparcours suffit, d'où un coût linéaire.\n"
            }
          ],
          "explanation": "Trier une table facilite la recherche de doublons : ils deviennent\nvoisins et un parcours linéaire suffit. Sans tri, il faudrait\ncomparer tous les couples, soit environ $n^2 / 2$ comparaisons."
        }
      ]
    },
    {
      "id": "q25",
      "difficulty": 3,
      "skills": [
        "synthese"
      ],
      "title": "Synthèse",
      "partie": "classes",
      "statement": "Parmi les affirmations suivantes sur la\ncomplexité, laquelle est **fausse** ?",
      "options": [
        {
          "text": "Le tri par sélection a un coût quadratique dans le pire cas",
          "correct": false,
          "feedback": "Vrai : il fait environ n²/2 comparaisons, quel que soit l'ordre\ninitial.\n"
        },
        {
          "text": "Un algorithme O(n) est toujours plus rapide qu'un algorithme O(n²)",
          "correct": true,
          "feedback": "Faux (donc bonne réponse) : O(n) est plus rapide pour de grandes\nentrées, mais sur de petites entrées un O(n²) avec une petite\nconstante peut battre un O(n) avec une grosse constante. Exemple :\navec n² opérations contre 100 × n, n² est plus petit tant que\nn < 100.\n"
        },
        {
          "text": "Pour les boucles imbriquées, on multiplie les complexités ; pour les séquences, on prend le maximum",
          "correct": false,
          "feedback": "Vrai : règle de calcul standard.\n"
        },
        {
          "text": "La complexité O ignore les constantes multiplicatives",
          "correct": false,
          "feedback": "Vrai : O(2n) = O(n) = O(100n).\n"
        }
      ],
      "explanation": "Maxime : « la notation O est un guide, pas une vérité absolue ».\nIl faut toujours tester en pratique pour les cas réels, surtout sur\nde petites entrées."
    },
    {
      "id": "q26",
      "difficulty": 2,
      "skills": [
        "while-decroissant"
      ],
      "title": "Boucle while soustractive",
      "partie": "analyse",
      "statement": "Quelle est la complexité de la fonction\nsuivante en fonction de `n` ?\n```python\ndef f(n):\n    while n > 0:\n        print(n)\n        n = n - 3\n```",
      "options": [
        {
          "text": "O(log n)",
          "correct": false,
          "feedback": "Erreur : la division par deux donne du\nlogarithmique. Ici on **soustrait** une\nconstante à chaque tour, donc le nombre\nd'itérations est proportionnel à `n`, pas à\n`log n`.\n"
        },
        {
          "text": "O(1)",
          "correct": false,
          "feedback": "Erreur : le nombre d'itérations dépend\nclairement de `n`. Pour `n = 30`, la boucle\ntourne dix fois ; pour `n = 300`, elle\ntourne cent fois. Ce n'est pas constant.\n"
        },
        {
          "text": "O(n)",
          "correct": true,
          "feedback": "Bonne réponse : `n` décroît par pas de 3.\nLe nombre d'itérations vaut environ `n / 3`,\nce qui est O(n) car on absorbe la constante\nmultiplicative. Toute boucle `while` qui\nsoustrait une constante à `n` est en O(n).\n"
        },
        {
          "text": "O(n²)",
          "correct": false,
          "feedback": "Erreur : il n'y a pas de double boucle. Le\ncoût est dominé par le nombre d'itérations\nde l'unique `while`.\n"
        }
      ],
      "explanation": "À retenir : soustraire une constante à `n` à\nchaque itération donne un coût linéaire ;\ndiviser `n` par une constante donne un coût\nlogarithmique. Cette distinction est la clé\npour reconnaître les algorithmes\nlogarithmiques."
    },
    {
      "id": "q27",
      "difficulty": 3,
      "skills": [
        "taille-vs-valeur"
      ],
      "title": "Taille vs valeur de l'entrée",
      "partie": "notion",
      "statement": "On considère la fonction qui compte les\nchiffres d'un entier positif :\n```python\ndef chiffres(n):\n    c = 0\n    while n > 0:\n        n = n // 10\n        c += 1\n    return c\n```\nQuelle est sa complexité ?",
      "options": [
        {
          "text": "O(n) en fonction de la valeur de n",
          "correct": false,
          "feedback": "Erreur : à chaque\nitération, `n` est divisé par 10. Donc le\nnombre d'itérations est de l'ordre de\n`log₁₀(n)`, pas de `n`. Pour `n = 10⁶`, la\nboucle ne tourne que sept fois, pas un\nmillion.\n"
        },
        {
          "text": "O(1) quelle que soit la valeur de n",
          "correct": false,
          "feedback": "Erreur : la boucle dépend de la valeur de\n`n`. Plus `n` est grand, plus elle tourne\nlongtemps.\n"
        },
        {
          "text": "O(log n) en fonction de la valeur de n",
          "correct": true,
          "feedback": "Bonne réponse : on divise `n` par 10 à\nchaque itération, donc `log₁₀(n)` étapes.\nC'est aussi exactement le **nombre de chiffres** de `n` dans son écriture\ndécimale. Une autre façon de le dire : la\ncomplexité est **linéaire** par rapport à\nla **taille** (nombre de chiffres) de `n`.\n"
        },
        {
          "text": "O(n²) en fonction de la valeur de n",
          "correct": false,
          "feedback": "Erreur : aucune double boucle ni structure\nquadratique. Le coût ne grandit pas comme\n`n²`.\n"
        }
      ],
      "explanation": "Subtilité importante : la « taille » d'un\nentier `n` est son **nombre de chiffres**,\ndonc de l'ordre de `log(n)`. Les algorithmes\nclassiques sur les entiers sont dits\n« linéaires » quand leur coût est\nproportionnel au nombre de chiffres, c'est-à-\ndire `O(log n)` en fonction de la valeur."
    },
    {
      "id": "q28",
      "difficulty": 2,
      "skills": [
        "operations-listes"
      ],
      "title": "Coût des opérations sur les listes",
      "partie": "analyse",
      "statement": "Parmi ces opérations sur une liste Python de\ntaille `n`, laquelle est la plus **coûteuse**\nau pire ?",
      "options": [
        {
          "text": "`liste[0]` (lecture du premier élément)",
          "correct": false,
          "feedback": "Erreur : l'accès par indice est en O(1) sur une liste Python, qui\nest un tableau. Pas besoin de parcourir.\n"
        },
        {
          "text": "`x in liste` (test de présence)",
          "correct": true,
          "feedback": "Bonne réponse : le test de présence est une\nrecherche linéaire cachée. Python compare\n`x` à chaque élément jusqu'à le trouver ; si\nla valeur est absente, il parcourt les `n`\néléments, d'où un coût en O(n) au pire. Un\n`in` répété sur une liste d'un million\nd'éléments devient très lent.\n"
        },
        {
          "text": "`liste.append(x)` (ajout en fin)",
          "correct": false,
          "feedback": "Erreur : `append` ajoute l'élément en fin de liste en temps\nconstant, sans parcourir les éléments déjà présents.\n"
        },
        {
          "text": "`len(liste)` (longueur de la liste)",
          "correct": false,
          "feedback": "Erreur : `len` est en O(1). Python connaît la longueur de la liste\nsans avoir à compter ses éléments.\n"
        }
      ],
      "explanation": "Repères à connaître pour les listes Python : accès par indice,\n`len` et `append` en O(1) ; `x in liste`, `max`, `min` et `sum` en\nO(n), car ils parcourent toute la liste au pire. Ces ordres de\ngrandeur guident l'écriture d'un algorithme efficace."
    },
    {
      "id": "q29",
      "difficulty": 3,
      "skills": [
        "seuils-pratiques"
      ],
      "title": "Choisir selon la taille",
      "partie": "notion",
      "statement": "Sur un PC moderne, on dispose d'**une seconde**\npour exécuter un algorithme (environ 10⁸ opérations\nPython). Quelle est la **plus grande taille**\nd'entrée que l'on peut traiter avec un\nalgorithme en O(n²) ?",
      "options": [
        {
          "text": "Environ 30 (trentaine)",
          "correct": false,
          "feedback": "Erreur : ce serait pour un algorithme exponentiel `O(2ⁿ)`. Avec\nn = 30, on atteint déjà environ `10⁹` opérations, au-delà du\nbudget. Pour un algorithme quadratique, on peut traiter beaucoup\nplus.\n"
        },
        {
          "text": "Environ 10⁸ (cent millions)",
          "correct": false,
          "feedback": "Erreur : ce serait pour un algorithme en\nO(n) (linéaire). Pour un algorithme\nquadratique, il faut prendre la racine\ncarrée du budget d'opérations.\n"
        },
        {
          "text": "Environ 10⁴ (dix mille)",
          "correct": true,
          "feedback": "Bonne réponse : on cherche `n` tel que `n² ≤ 10⁸`, donc\n`n ≤ √(10⁸) = 10⁴`. À retenir : un algorithme quadratique devient\nlent dès quelques dizaines de milliers d'éléments.\n"
        },
        {
          "text": "Environ 10⁶ (un million)",
          "correct": false,
          "feedback": "Erreur : pour `n = 10⁶`, un algorithme\nquadratique fait `10¹²` opérations, ce qui\nprendrait environ trois heures. Hors\nbudget.\n"
        }
      ],
      "explanation": "Repères de seuils pratiques (1 seconde de calcul, environ 10⁸\nopérations) : O(2ⁿ) → n ≈ 25 ; O(n³) → n ≈ 500 ; O(n²) → n ≈ 10⁴ ;\nO(n) → n ≈ 10⁸ ; O(log n) → n gigantesque. Indispensable pour\névaluer la faisabilité d'un algorithme avant de l'écrire."
    },
    {
      "id": "q30",
      "difficulty": 2,
      "skills": [
        "boucles-imbriquees",
        "quadratique",
        "analyse"
      ],
      "title": "Une boucle intérieure qui grandit",
      "partie": "analyse",
      "statement": "Combien de fois l'instruction `compteur = compteur + 1` est-elle\nexécutée, en fonction de `n` ?\n```python\ncompteur = 0\nfor i in range(n):\n    for j in range(i):\n        compteur = compteur + 1\n```",
      "options": [
        {
          "text": "$n(n-1)/2$ fois, soit un coût quadratique",
          "correct": true,
          "feedback": "Bonne réponse : la boucle intérieure fait $0$, puis $1$, puis\n$2$ tours, jusqu'à $n - 1$. La somme vaut $n(n-1)/2$, de\nl'ordre de $n^2 / 2$.\n"
        },
        {
          "text": "$n$ fois, soit un coût linéaire",
          "correct": false,
          "feedback": "Erreur : la boucle intérieure multiplie le travail ; seule la\nboucle extérieure fait $n$ tours.\n"
        },
        {
          "text": "$n^2$ fois exactement, soit un coût quadratique",
          "correct": false,
          "feedback": "Erreur : $n^2$ serait le compte avec `range(n)` à l'intérieur ;\n`range(i)` en fait environ la moitié. L'ordre de grandeur reste\nquadratique.\n"
        },
        {
          "text": "$2n$ fois, soit un coût linéaire",
          "correct": false,
          "feedback": "Erreur : deux boucles imbriquées se multiplient, elles ne\ns'additionnent pas.\n"
        }
      ],
      "explanation": "Que la boucle intérieure fasse $n$ ou $i$ tours, le coût total reste\nen $O(n^2)$ : le facteur $1/2$ ne change pas la classe."
    },
    {
      "id": "q31",
      "difficulty": 2,
      "skills": [
        "boucle-constante",
        "lineaire",
        "analyse"
      ],
      "title": "Une boucle intérieure de taille fixe",
      "partie": "analyse",
      "statement": "Quelle est la classe de complexité de ce programme en fonction de\n`n` ?\n```python\nfor i in range(n):\n    for j in range(10):\n        total = total + t[i] * j\n```",
      "options": [
        {
          "text": "$O(n)$, la boucle intérieure étant de taille fixe",
          "correct": true,
          "feedback": "Bonne réponse : $10n$ opérations, et une constante ne change\npas la classe. Le coût grandit comme $n$.\n"
        },
        {
          "text": "$O(n^2)$, deux boucles étant imbriquées l'une dans l'autre",
          "correct": false,
          "feedback": "Erreur : l'imbrication ne suffit pas ; il faut que les deux\nboucles dépendent de $n$. Ici la seconde fait toujours dix tours.\n"
        },
        {
          "text": "$O(10n)$, une classe à part entre linéaire et quadratique",
          "correct": false,
          "feedback": "Erreur : les constantes multiplicatives disparaissent dans la\nnotation $O$ ; $O(10n)$ se note $O(n)$.\n"
        },
        {
          "text": "$O(1)$, le nombre dix étant fixé une fois pour toutes",
          "correct": false,
          "feedback": "Erreur : la boucle extérieure dépend de $n$, le coût n'est pas\nconstant.\n"
        }
      ],
      "explanation": "Pour classer un programme, on ne garde que ce qui dépend de la\ntaille de l'entrée : les boucles de taille fixe comptent pour une\nconstante."
    },
    {
      "id": "q32",
      "difficulty": 3,
      "skills": [
        "appel",
        "boucle",
        "analyse",
        "quadratique"
      ],
      "title": "Une recherche dans une boucle",
      "partie": "analyse",
      "statement": "La fonction `appartient(x, t)` fait une recherche linéaire dans la\nliste `t`, de taille `n`. Quelle est la classe de complexité de ce\nprogramme ?\n```python\nfor i in range(n):\n    if appartient(t[i], u):\n        nb = nb + 1\n```\nLa liste `u` est elle aussi de taille `n`.",
      "options": [
        {
          "text": "$O(n^2)$, chaque tour appelant une fonction de coût linéaire",
          "correct": true,
          "feedback": "Bonne réponse : $n$ tours, et chacun parcourt jusqu'à $n$\néléments de `u`. Le coût total est de l'ordre de $n \\times n$.\n"
        },
        {
          "text": "$O(n)$, le programme ne contenant qu'une boucle",
          "correct": false,
          "feedback": "Erreur : la boucle cachée dans `appartient` compte aussi.\n"
        },
        {
          "text": "$O(1)$, un appel de fonction étant une opération élémentaire",
          "correct": false,
          "feedback": "Erreur : un appel coûte ce que coûte la fonction appelée, ici un\nparcours de `u`.\n"
        },
        {
          "text": "$O(\\log n)$, la fonction cherchant par dichotomie",
          "correct": false,
          "feedback": "Erreur : la recherche est linéaire, et même dichotomique, la\nboucle extérieure resterait en $n$.\n"
        }
      ],
      "explanation": "Le coût d'un appel de fonction n'est pas constant : il faut le\nmultiplier par le nombre d'appels."
    },
    {
      "id": "q33",
      "difficulty": 1,
      "skills": [
        "classes",
        "dictionnaire",
        "acces"
      ],
      "title": "Accéder à une valeur par sa clé",
      "partie": "classes",
      "statement": "Quelle est la classe de complexité de l'accès `d[cle]` dans un\ndictionnaire Python de $n$ entrées ?",
      "options": [
        {
          "text": "$O(1)$, le temps ne dépendant pas de $n$",
          "correct": true,
          "feedback": "Bonne réponse : un dictionnaire calcule directement où ranger\nchaque clé, ce qui rend l'accès constant en moyenne, quel que\nsoit $n$.\n"
        },
        {
          "text": "$O(n)$, toutes les clés devant être comparées",
          "correct": false,
          "feedback": "Erreur : c'est le coût d'une recherche dans une liste, pas dans\nun dictionnaire.\n"
        },
        {
          "text": "$O(\\log n)$, les clés étant triées en interne",
          "correct": false,
          "feedback": "Erreur : les clés d'un dictionnaire ne sont pas triées, et\nl'accès est encore plus rapide qu'une dichotomie.\n"
        },
        {
          "text": "$O(n^2)$, chaque clé étant comparée à toutes les autres",
          "correct": false,
          "feedback": "Erreur : aucune double boucle n'intervient dans un accès par\nclé.\n"
        }
      ],
      "explanation": "Choisir un dictionnaire plutôt qu'une liste transforme une\nrecherche linéaire en accès constant."
    },
    {
      "id": "q34",
      "difficulty": 2,
      "skills": [
        "quadratique",
        "temps",
        "calcul"
      ],
      "title": "Estimer le temps d'un algorithme quadratique",
      "partie": "classes",
      "statement": "Un algorithme en $O(n^2)$ traite $1\\,000$ éléments en $1$ s.\nCombien de temps lui faut-il pour $10\\,000$ éléments ?",
      "options": [
        {
          "text": "Environ $100$ s",
          "correct": true,
          "feedback": "Bonne réponse : la taille est multipliée par dix, donc le temps\npar dix au carré, soit $100$.\n"
        },
        {
          "text": "Environ $10$ s",
          "correct": false,
          "feedback": "Erreur : un temps multiplié par dix correspondrait à un\nalgorithme linéaire.\n"
        },
        {
          "text": "Toujours $1$ s",
          "correct": false,
          "feedback": "Erreur : un coût quadratique dépend fortement de la taille.\n"
        },
        {
          "text": "Environ $100 \\times 100$ s",
          "correct": false,
          "feedback": "Erreur : le facteur est le carré du rapport des tailles, pas\nson carré au carré.\n"
        }
      ],
      "explanation": "Connaître la classe d'un algorithme permet d'extrapoler : en\n$O(n^2)$, multiplier la taille par $k$ multiplie le temps par\n$k^2$.",
      "variants": [
        {
          "statement": "Un algorithme en $O(n^2)$ traite $1\\,000$ éléments en $1$ s.\nCombien de temps lui faut-il pour $10\\,000$ éléments ?",
          "options": [
            {
              "text": "Environ $100$ s",
              "correct": true,
              "feedback": "Bonne réponse : la taille est multipliée par dix, donc le temps\npar dix au carré, soit $100$.\n"
            },
            {
              "text": "Environ $10$ s",
              "correct": false,
              "feedback": "Erreur : un temps multiplié par dix correspondrait à un\nalgorithme linéaire.\n"
            },
            {
              "text": "Toujours $1$ s",
              "correct": false,
              "feedback": "Erreur : un coût quadratique dépend fortement de la taille.\n"
            },
            {
              "text": "Environ $100 \\times 100$ s",
              "correct": false,
              "feedback": "Erreur : le facteur est le carré du rapport des tailles, pas\nson carré au carré.\n"
            }
          ],
          "explanation": "Connaître la classe d'un algorithme permet d'extrapoler : en\n$O(n^2)$, multiplier la taille par $k$ multiplie le temps par\n$k^2$."
        },
        {
          "statement": "Un algorithme en $O(n^2)$ traite $1\\,000$ éléments en $1$ s.\nCombien de temps lui faut-il pour $5\\,000$ éléments ?",
          "options": [
            {
              "text": "Environ $25$ s",
              "correct": true,
              "feedback": "Bonne réponse : la taille est multipliée par cinq, donc le temps\npar cinq au carré, soit $25$.\n"
            },
            {
              "text": "Environ $5$ s",
              "correct": false,
              "feedback": "Erreur : un temps multiplié par cinq correspondrait à un\nalgorithme linéaire.\n"
            },
            {
              "text": "Toujours $1$ s",
              "correct": false,
              "feedback": "Erreur : un coût quadratique dépend fortement de la taille.\n"
            },
            {
              "text": "Environ $25 \\times 25$ s",
              "correct": false,
              "feedback": "Erreur : le facteur est le carré du rapport des tailles, pas\nson carré au carré.\n"
            }
          ],
          "explanation": "Connaître la classe d'un algorithme permet d'extrapoler : en\n$O(n^2)$, multiplier la taille par $k$ multiplie le temps par\n$k^2$."
        },
        {
          "statement": "Un algorithme en $O(n^2)$ traite $1\\,000$ éléments en $1$ s.\nCombien de temps lui faut-il pour $2\\,000$ éléments ?",
          "options": [
            {
              "text": "Environ $4$ s",
              "correct": true,
              "feedback": "Bonne réponse : la taille est multipliée par deux, donc le temps\npar deux au carré, soit $4$.\n"
            },
            {
              "text": "Environ $2$ s",
              "correct": false,
              "feedback": "Erreur : un temps multiplié par deux correspondrait à un\nalgorithme linéaire.\n"
            },
            {
              "text": "Toujours $1$ s",
              "correct": false,
              "feedback": "Erreur : un coût quadratique dépend fortement de la taille.\n"
            },
            {
              "text": "Environ $4 \\times 4$ s",
              "correct": false,
              "feedback": "Erreur : le facteur est le carré du rapport des tailles, pas\nson carré au carré.\n"
            }
          ],
          "explanation": "Connaître la classe d'un algorithme permet d'extrapoler : en\n$O(n^2)$, multiplier la taille par $k$ multiplie le temps par\n$k^2$."
        }
      ]
    },
    {
      "id": "q35",
      "difficulty": 3,
      "skills": [
        "classes",
        "comparaison",
        "calcul"
      ],
      "title": "Deux tris sur un million d'éléments",
      "partie": "classes",
      "statement": "Pour $n = 10^6$, on compare un tri en $O(n \\log_2 n)$ et un tri en\n$O(n^2)$, avec la même constante. Quel est l'ordre de grandeur du\nrapport de leurs coûts ?",
      "options": [
        {
          "text": "Environ $50\\,000$, soit $n / \\log_2 n$",
          "correct": true,
          "feedback": "Bonne réponse : $n^2 = 10^{12}$ et $n \\log_2 n \\approx 2 \\times\n10^7$. Le rapport vaut $10^{12} / (2 \\times 10^7) = 5 \\times\n10^4$.\n"
        },
        {
          "text": "Environ $2$, les deux étant presque linéaires",
          "correct": false,
          "feedback": "Erreur : $n^2$ n'a rien de linéaire ; l'écart est énorme pour\n$n$ grand.\n"
        },
        {
          "text": "Environ $10^6$, soit $n$, le rapport des tailles",
          "correct": false,
          "feedback": "Erreur : le rapport est $n / \\log_2 n$, pas $n$, car le tri en\n$n \\log_2 n$ ne coûte pas $n$.\n"
        },
        {
          "text": "Environ $20$, soit $\\log_2 n$, le facteur du tri efficace",
          "correct": false,
          "feedback": "Erreur : $20$ est le rapport entre $n \\log_2 n$ et $n$, pas\nentre $n^2$ et $n \\log_2 n$.\n"
        }
      ],
      "explanation": "Sur un million d'éléments, un tri quadratique est des dizaines de\nmilliers de fois plus lent qu'un tri en $n \\log n$ : la classe de\ncomplexité décide de ce qui est faisable."
    },
    {
      "id": "q36",
      "difficulty": 1,
      "skills": [
        "notion",
        "mesure",
        "independance"
      ],
      "title": "Compter plutôt que chronométrer",
      "partie": "notion",
      "statement": "Pourquoi évalue-t-on le coût d'un algorithme en comptant des\nopérations plutôt qu'en mesurant un temps d'exécution ?",
      "options": [
        {
          "text": "Parce que le compte ne dépend ni de la machine ni du langage",
          "correct": true,
          "feedback": "Bonne réponse : un chronomètre mesure un programme sur une\nmachine donnée ; le nombre d'opérations décrit l'algorithme\nlui-même et permet de comparer des méthodes.\n"
        },
        {
          "text": "Parce que les temps d'exécution sont impossibles à mesurer",
          "correct": false,
          "feedback": "Erreur : on mesure très bien un temps, mais il ne caractérise\npas l'algorithme.\n"
        },
        {
          "text": "Parce que les opérations sont toutes de même durée",
          "correct": false,
          "feedback": "Erreur : elles ne durent pas exactement le même temps ; on les\nsuppose bornées, ce qui suffit pour un ordre de grandeur.\n"
        },
        {
          "text": "Parce que le nombre d'opérations est toujours plus petit que le temps",
          "correct": false,
          "feedback": "Erreur : les deux grandeurs ne se comparent pas.\n"
        }
      ],
      "explanation": "La complexité est une propriété de l'algorithme, pas de\nl'ordinateur qui l'exécute."
    },
    {
      "id": "q37",
      "difficulty": 2,
      "skills": [
        "notion",
        "operation-elementaire"
      ],
      "title": "Ce qu'est une opération élémentaire",
      "partie": "notion",
      "statement": "Dans le décompte du coût d'un algorithme, que considère-t-on comme\nune opération élémentaire ?",
      "options": [
        {
          "text": "Une affectation, une comparaison ou un calcul, de durée bornée",
          "correct": true,
          "feedback": "Bonne réponse : ces opérations prennent un temps qui ne dépend\npas de la taille des données. Les compter donne l'ordre de\ngrandeur du coût.\n"
        },
        {
          "text": "Un tour de boucle complet, quel que soit son contenu",
          "correct": false,
          "feedback": "Erreur : un tour de boucle peut contenir d'autres boucles ou\ndes appels coûteux.\n"
        },
        {
          "text": "Un appel de fonction, quel que soit le coût de la fonction appelée",
          "correct": false,
          "feedback": "Erreur : un appel coûte ce que coûte la fonction, qui peut\nparcourir toute une liste.\n"
        },
        {
          "text": "Une ligne de code Python, quelle que soit l'instruction qu'elle contient",
          "correct": false,
          "feedback": "Erreur : une seule ligne peut cacher un parcours entier, comme\n`x in liste`.\n"
        }
      ],
      "explanation": "Compter les opérations élémentaires demande de voir ce que cache\nchaque instruction : une boucle, un appel ou un test d'appartenance\npeuvent valoir $n$ opérations."
    }
  ]
}