Optimizar no es solo micro-ajustar código: consiste en reducir el orden de crecimiento eligiendo estructuras, cortando trabajo innecesario y simplificando la recursión. Estas pautas ayudan a mejorar el Big-O sin perder claridad.
Un doble loop sin dependencias puede reemplazarse por una sola pasada si combinamos condiciones.
// Ineficiente: dos recorridos
int contar_pares(const int *v, int n) {
int pares = 0, impares = 0;
for (int i = 0; i < n; i++) if (v[i] % 2 == 0) pares++;
for (int i = 0; i < n; i++) if (v[i] % 2 != 0) impares++;
return pares + impares;
}
// Mejor: un solo loop O(n)
int contar_pares_una_pasada(const int *v, int n) {
int pares = 0, impares = 0;
for (int i = 0; i < n; i++) {
if (v[i] % 2 == 0) pares++;
else impares++;
}
return pares + impares;
}
Salir temprano reduce el peor caso si la condición favorable es frecuente.
int contiene(const int *v, int n, int objetivo) {
for (int i = 0; i < n; i++) {
if (v[i] == objetivo) {
// mejor caso O(1) si está al inicio
return 1;
}
}
return 0; // peor caso O(n)
}
Guardar resultados intermedios evita recomputar valores caros.
// Prefijo de sumas para responder rangos en O(1)
void prefijos(const int *v, int n, int *p) {
p[0] = 0;
for (int i = 0; i < n; i++) p[i + 1] = p[i] + v[i];
}
int suma_rango(const int *p, int l, int r) { // r exclusivo
return p[r] - p[l];
}
Calcular los prefijos cuesta O(n); luego cada consulta pasa de O(n) a O(1).
O(n^2).// Fibonacci con memo
int fib(int n, int *memo) {
if (n <= 1) return n;
if (memo[n] != -1) return memo[n];
memo[n] = fib(n - 1, memo) + fib(n - 2, memo);
return memo[n];
}
O(log n) en lugar de buscar lineal.O(1) en vez de listas.O(log n)).Copiar buffers grandes o hacer swaps innecesarios multiplica el costo.
void intercambiar(int *a, int *b) {
if (a == b) return; // evita swap trivial
int tmp = *a;
*a = *b;
*b = tmp;
}
En estructuras grandes, trabaja con punteros o índices para evitar duplicar memoria.
Problema: contar cuántos pares de elementos iguales hay en un array.
// O(n^2): compara cada par
int contar_pares_cuadratico(const int *v, int n) {
int pares = 0;
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (v[i] == v[j]) pares++;
}
}
return pares;
}
// O(n log n): ordenar y agrupar
int comparar_ints(const void *a, const void *b) {
int ia = *(const int*)a, ib = *(const int*)b;
return (ia > ib) - (ia < ib);
}
int contar_pares_ordenando(int *v, int n) {
qsort(v, n, sizeof(int), comparar_ints); // O(n log n)
int pares = 0;
for (int i = 0; i < n; ) {
int j = i + 1;
while (j < n && v[j] == v[i]) j++;
int repeticiones = j - i;
pares += (repeticiones * (repeticiones - 1)) / 2;
i = j;
}
return pares;
}
Ordenar permite agrupar iguales y contarlos en una sola pasada. El costo dominante pasa a ser el sort (O(n log n)), mejor que el doble loop cuadrático para n grande.