Numărarea permutărilor — n factorial

Mediu~15 min10 pași

De ce contează?

Ai 3 cărți distincte și un raft. În câte ordini diferite le poți așeza? Pui o carte prima, alta a doua, ultima rămasă a treia — și de fiecare dată ai mai puține opțiuni. Numărând cu grijă toate aranjările, descoperi factorialul: cea mai naturală formulă de numărare din toată combinatorica.

De ce n!

O permutare a n elemente distincte este o aranjare a lor într-o ordine. Întrebarea „câte permutări există?” se rezolvă gândind poziție cu poziție.

Imaginează-ți n locuri goale pe care le umpli de la stânga la dreapta:

  • pentru prima poziție poți alege oricare dintre cele n elemente → n variante;
  • pentru a doua poziție a mai rămas un element mai puțin → n-1 variante;
  • pentru a treian-2 variante;
  • ... și tot așa, până la ultima poziție, unde a rămas un singur element → 1 variantă.

Fiindcă alegerile sunt independente, le înmulțești:

n · (n-1) · (n-2) · ... · 2 · 1 = n!

Asta este definiția lui n factorial. El numără exact câte ordini distincte poți forma cu n obiecte diferite.

Exemplu pas cu pas: n = 3

Fie cele 3 elemente a, b, c. Le enumerăm complet, fixând pe rând primul element:

  • începe cu a: abc, acb
  • începe cu b: bac, bca
  • începe cu c: cab, cba

Sunt exact 6 permutări, iar 3! = 3 · 2 · 1 = 6. Se vede și logica pozițiilor: 3 alegeri pentru primul element, apoi câte 2 pentru al doilea, apoi 1 pentru ultimul → 3 · 2 · 1.

Cazul special: pentru n = 0 avem 0! = 1. Există o singură modalitate de a aranja zero elemente — șirul gol. Convenția nu este arbitrară: ea face ca formula n! = n · (n-1)! să rămână corectă și pentru n = 1 (1! = 1 · 0! = 1).

Implementare C++

Factorial iterativ cu long long — corect pentru valori mici (n până la 20):

#include <iostream>
using namespace std;

long long factorial(int n) {
    long long rezultat = 1;
    for (int i = 2; i <= n; i++) {
        rezultat = rezultat * i;    // inmultim pe rand: 2, 3, ..., n
    }
    return rezultat;                // pentru n = 0 sau n = 1 ramane 1
}

int main() {
    cout << factorial(3) << "\n";   // 6
    cout << factorial(0) << "\n";   // 1 (conventia 0! = 1)
    cout << factorial(5) << "\n";   // 120
    return 0;
}

Pentru n mare enunțul cere răspunsul modulo 1000000007, fiindcă valoarea reală nu mai încape în niciun tip întreg:

#include <iostream>
using namespace std;

const long long MOD = 1000000007;

long long factorialMod(int n) {
    long long rezultat = 1;
    for (int i = 2; i <= n; i++) {
        rezultat = (rezultat * i) % MOD;    // reducem dupa fiecare inmultire
    }
    return rezultat;
}

int main() {
    cout << factorialMod(3) << "\n";        // 6
    cout << factorialMod(20) << "\n";       // 2432902008176640000 % MOD
    cout << factorialMod(100000) << "\n";   // valoare mica, fara overflow
    return 0;
}

Reducem cu % MOD după fiecare înmulțire, nu doar la final: așa fiecare factor intermediar rămâne mai mic decât MOD, iar produsul a două astfel de valori încape lejer în long long.

Complexitate

OperațieTimpSpațiu
factorial iterativO(n)O(1)
factorial moduloO(n)O(1)

O singură buclă care înmulțește de la 2 la n: liniar în n, fără memorie suplimentară dincolo de variabila rezultat.

Observația-cheie

0! = 1 și 1! = 1. Cele două cazuri de bază pornesc bucla de la i = 2, deci codul le tratează automat: rezultat rămâne 1 când n este 0 sau 1.

Greșeli frecvente
  • Tratezi greșit 0!: dacă pornești bucla de la i = 0 sau i = 1, înmulțești cu 0 și obții 0 în loc de 1. Pornește de la i = 2 și inițializează cu 1.
  • Overflow la ~21!: 20! este ultima valoare care încape în long long; 21! depășește 9.2 · 10^18. Dacă enunțul are n mare, folosește varianta modulo, altfel rezultatul este corupt în tăcere.
  • Reduci modulo doar la final: produsul rezultat * i poate da deja overflow înainte să aplici % MOD. Aplică modulo după fiecare înmulțire.
  • Confunzi permutările cu combinările sau aranjamentele: permutarea ține cont de ordine și folosește toate elementele; combinarea ignoră ordinea, iar aranjamentul ia doar k din n.
optiuni
3
2
1
poz1
poz2
poz3
Pentru n = 3: 3 alegeri la prima pozitie, 2 la a doua, 1 la ultima. Produsul 3 · 2 · 1 = 6 da numarul de permutari.

Permutarea este, de fapt, un aranjament de n din n: iei toate cele n elemente și le pui în ordine. De aceea P(n) = A(n, n) = n!.

Recapitulare

  • Numărul de permutări ale n elemente distincte este n! = n · (n-1) · ... · 1, prin argumentul alegerilor poziție cu poziție.
  • Prin convenție 0! = 1, ceea ce păstrează valabilă relația n! = n · (n-1)!.
  • La n mare folosești long long plus % MOD (cu MOD = 1000000007) după fiecare înmulțire, fiindcă 21! deja depășește long long.

Întrebarea 1 / 3

Câte permutări distincte are un șir de 4 elemente diferite?