Какая временная сложность у алгоритма полного перебора с двумя вложенными циклами?

Ответ

Временная сложность классического полного перебора с двумя вложенными циклами, каждый из которых зависит от размера входных данных n, составляет O(n²) (квадратичная сложность).

Пример на JavaScript/Node.js:

// O(n²)
for (let i = 0; i < n; i++) {       // Внешний цикл: O(n)
  for (let j = 0; j < n; j++) {     // Внутренний цикл: O(n)
    // Какая-то операция O(1)
    console.log(`Pair: ${i}, ${j}`);
  }
}
// Общее количество итераций: n * n = n²

Вариации:

  • Если размеры массивов разные (n и m), сложность будет *O(n m)**.
    // O(n * m)
    const arr1 = new Array(n);
    const arr2 = new Array(m);
    for (let i = 0; i < arr1.length; i++) {
    for (let j = 0; j < arr2.length; j++) {
    // Операция
    }
    }
  • Если внутренний цикл начинается с i+1 (например, для сравнения всех уникальных пар), сложность остаётся O(n²), но количество итераций будет примерно n²/2, что в асимптотическом анализе всё равно O(n²).

Квадратичная сложность считается неэффективной для больших данных, и в production-коде на Node.js для подобных задач часто ищут оптимизации (кеширование, хеш-таблицы, более эффективные алгоритмы).

Ответ 18+ 🔞

Смотри, тут всё просто, как три копейки. Если у тебя в коде сидят два цикла, один в другом, и оба гоняются по всему массиву размером n — это классический пиздец, который мы называем O(n²). Квадратичная сложность, ёпта. Это когда твой алгоритм жрёт ресурсы, как не в себя, и на больших данных начинает тормозить так, что хоть святых выноси.

Вот тебе наглядный пример на Node.js, чтобы не было вопросов:

// O(n²)
for (let i = 0; i < n; i++) {       // Внешний цикл: O(n)
  for (let j = 0; j < n; j++) {     // Внутренний цикл: O(n)
    // Какая-то операция O(1)
    console.log(`Pair: ${i}, ${j}`);
  }
}
// Общее количество итераций: n * n = n²

Видишь? Каждый раз, когда внешний цикл делает шаг, внутренний пробегает весь массив снова. Получается n раз по n итераций. Это и есть тот самый квадрат, от которого потом волосы дыбом встают.

Но жизнь, она же хитрая жопа, бывают нюансы:

  • Если массивы разного размера (n и m), то сложность будет *O(n m)**. Не квадрат, конечно, но тоже не подарок.
    // O(n * m)
    const arr1 = new Array(n);
    const arr2 = new Array(m);
    for (let i = 0; i < arr1.length; i++) {
    for (let j = 0; j < arr2.length; j++) {
    // Операция
    }
    }
  • Бывает, внутренний цикл стартует не с нуля, а с i+1 (чтобы сравнить все уникальные пары). Количество операций упадёт примерно вдвое, до n²/2, но в большой нотации O(n²) это всё равно считается. Потому что константы (типа деления на 2) на асимптотику, блядь, не влияют.

Короче, запомни: если видишь вложенные циклы по одним и тем же данным — это красная лампочка. В продакшене на Node.js такую хуйню обычно стараются переписать: использовать хеш-таблицы, кеширование или ещё какую умную оптимизацию, чтобы не получить овердохуища запросов и не накрыться медным тазом.