时间空间复杂度基础

理解算法的时间复杂度与空间复杂度

问题

什么是时间复杂度和空间复杂度?如何分析一段代码的复杂度?

解答

时间复杂度

时间复杂度描述算法执行时间随输入规模增长的变化趋势,用大 O 表示法。

// O(1) - 常数时间
function getFirst(arr) {
  return arr[0]; // 无论数组多大,只执行一次
}

// O(n) - 线性时间
function sum(arr) {
  let total = 0;
  for (let i = 0; i < arr.length; i++) {
    total += arr[i]; // 执行 n 次
  }
  return total;
}

// O(n²) - 平方时间
function bubbleSort(arr) {
  const n = arr.length;
  for (let i = 0; i < n; i++) {        // 外层 n 次
    for (let j = 0; j < n - i - 1; j++) { // 内层 n 次
      if (arr[j] > arr[j + 1]) {
        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
      }
    }
  }
  return arr;
}

// O(log n) - 对数时间
function binarySearch(arr, target) {
  let left = 0;
  let right = arr.length - 1;
  
  while (left <= right) {
    const mid = Math.floor((left + right) / 2);
    if (arr[mid] === target) return mid;
    if (arr[mid] < target) {
      left = mid + 1;  // 每次排除一半
    } else {
      right = mid - 1;
    }
  }
  return -1;
}

// O(n log n) - 线性对数时间
function mergeSort(arr) {
  if (arr.length <= 1) return arr;
  
  const mid = Math.floor(arr.length / 2);
  const left = mergeSort(arr.slice(0, mid));   // log n 层递归
  const right = mergeSort(arr.slice(mid));
  
  return merge(left, right); // 每层 O(n) 合并
}

function merge(left, right) {
  const result = [];
  let i = 0, j = 0;
  
  while (i < left.length && j < right.length) {
    if (left[i] <= right[j]) {
      result.push(left[i++]);
    } else {
      result.push(right[j++]);
    }
  }
  
  return result.concat(left.slice(i)).concat(right.slice(j));
}

空间复杂度

空间复杂度描述算法占用内存随输入规模增长的变化趋势。

// O(1) - 常数空间
function findMax(arr) {
  let max = arr[0]; // 只用一个变量
  for (let i = 1; i < arr.length; i++) {
    if (arr[i] > max) max = arr[i];
  }
  return max;
}

// O(n) - 线性空间
function copyArray(arr) {
  const copy = []; // 新数组大小与输入成正比
  for (let i = 0; i < arr.length; i++) {
    copy.push(arr[i]);
  }
  return copy;
}

// O(n) - 递归调用栈
function factorial(n) {
  if (n <= 1) return 1;
  return n * factorial(n - 1); // 调用栈深度为 n
}

// O(n²) - 二维数组
function createMatrix(n) {
  const matrix = [];
  for (let i = 0; i < n; i++) {
    matrix[i] = new Array(n).fill(0); // n × n 空间
  }
  return matrix;
}

常见复杂度对比

复杂度名称示例
O(1)常数数组访问、哈希表查找
O(log n)对数二分查找
O(n)线性遍历数组
O(n log n)线性对数快排、归并排序
O(n²)平方冒泡排序、嵌套循环
O(2ⁿ)指数递归斐波那契

分析技巧

// 1. 顺序执行:取最大
function example1(arr) {
  let sum = 0;
  for (let x of arr) sum += x;    // O(n)
  for (let x of arr) sum += x;    // O(n)
  return sum;                      // 总体 O(n)
}

// 2. 嵌套循环:相乘
function example2(arr) {
  for (let i = 0; i < arr.length; i++) {      // O(n)
    for (let j = 0; j < arr.length; j++) {    // O(n)
      console.log(arr[i], arr[j]);            // 总体 O(n²)
    }
  }
}

// 3. 条件分支:取最大
function example3(arr, flag) {
  if (flag) {
    for (let x of arr) console.log(x);        // O(n)
  } else {
    for (let i of arr) {
      for (let j of arr) console.log(i, j);   // O(n²)
    }
  }
  // 总体 O(n²)
}

关键点

  • 时间复杂度关注执行次数,空间复杂度关注内存占用
  • 大 O 表示法只保留最高阶项,忽略常数和低阶项
  • 递归算法要考虑调用栈的空间消耗
  • 分析时关注循环次数和嵌套层数
  • 常见排序:O(n²) 冒泡/选择,O(n log n) 快排/归并