大家好,我是算法探险家FogLetter,今天要带大家彻底搞懂算法学习的核心要点!很多刚接触编程的小伙伴都会对"算法"这个词感到既熟悉又陌生。熟悉是因为总听别人提起,陌生是因为不知道它到底是什么。简单来说,算法就是解决问题的明确步骤和方法。就像做菜的食谱一样,告诉你第一步做什么,第二步做什么,最终做出美味佳肴。
📌 1. 算法学习路线:从入门到Offer
1.1 为什么要学习算法?
- 大厂敲门砖:几乎所有一线互联网公司的技术面试都会考察算法能力
- 编程思维训练:培养逻辑思维和问题解决能力
- 代码优化:写出更高效、更优雅的代码
- 职业发展:是进阶高级工程师的必经之路
1.2 高效学习路径
- LeetCode热题100:大厂高频考题集中营
- 代码随想录:系统性算法学习宝典
"我带的实习生靠这两样拿了6个大厂offer" —— 某字节跳动技术总监
📌 2. 算法效率的黄金标准
2.1 时间复杂度:你的代码跑得有多快?
时间复杂度是用来估计算法运行时间的一个指标。我们常用大O表示法来描述时间复杂度。
常见复杂度排序: O(1) < O(logn) < O(n) < O(nlogn) < O(n²) < O(2ⁿ) < O(n!)
常见的时间复杂度(从快到慢排列):
-
O(1) :常数时间复杂度
function getFirstElement(arr) { return arr[0]; // 无论数组多长,都只执行一次操作 } -
O(logn) :对数时间复杂度
// 二分查找就是典型的O(logn)算法 function binarySearch(arr, target) { let left = 0, 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) :线性时间复杂度
// 遍历数组就是O(n) function traverse(arr) { for (let i = 0; i < arr.length; i++) { console.log(arr[i]); } } -
O(nlogn) :线性对数时间复杂度
// 快速排序、归并排序都是O(nlogn) -
O(n²) :平方时间复杂度
// 冒泡排序就是O(n²) function bubbleSort(arr) { for (let i = 0; i < arr.length; i++) { for (let j = 0; j < arr.length - i - 1; j++) { if (arr[j] > arr[j + 1]) { [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]; } } } return arr; } -
O(2ⁿ) :指数时间复杂度
// 斐波那契数列的递归实现就是O(2ⁿ) function fibonacci(n) { if (n <= 1) return n; return fibonacci(n - 1) + fibonacci(n - 2); } -
O(n!) :阶乘时间复杂度
// 旅行商问题的暴力解法就是O(n!)
计算法则(三步走):
- 计算执行次数T(n)
- 忽略常数项、低次项、系数
- 取最高阶项
🌰 示例分析:
function example(arr) {
let sum = 0; // 1次
for (let i = 0; i < arr.length; i++) { // 1+(n+1)+n次
sum += arr[i]; // n次
}
for (let i = 0; i < arr.length; i++) { // 1+(n+1)+n次
for (let j = 0; j < arr.length; j++) { // n*(1+(n+1)+n)
console.log(arr[i], arr[j]); // n*n次
}
}
return sum; // 1次
}
// T(n) = 1 + (1+(n+1)+n) + n + (1+(n+1)+n) + n*(1+(n+1)+n) + n² + 1
// = 3n + 4 + 3n² + 3n + 1
// = 3n² + 6n + 5
// ≈ O(n²)
2.2 空间复杂度:你的代码吃了多少内存?
空间复杂度是对算法在运行过程中临时占用存储空间大小的度量。
算法运行过程中占用的额外空间大小,记作S(n)
常见的空间复杂度:
-
O(1) :常数空间
function sum(arr) { let result = 0; // 只用了固定数量的变量 for (let num of arr) { result += num; } return result; } -
O(n) :线性空间
function copyArray(arr) { let newArr = []; // 需要和输入数组同样大小的空间 for (let i = 0; i < arr.length; i++) { newArr[i] = arr[i]; } return newArr; } -
O(n²) :平方空间
function generateMatrix(n) { let matrix = []; for (let i = 0; i < n; i++) { matrix[i] = []; // 创建n×n的矩阵 for (let j = 0; j < n; j++) { matrix[i][j] = i * j; } } return matrix; }
📌 3. 数据结构:算法的基石
3.1 线性结构
| 结构 | 特点 | 应用场景 |
|---|---|---|
| 数组 | 随机访问O(1) | 高频查询 |
| 链表 | 动态内存分配 | 频繁增删 |
| 栈 | LIFO(后进先出) | 函数调用栈 |
| 队列 | FIFO(先进先出) | 消息队列 |
3.1.1 数组(Array)
特点:
- 连续的内存空间
- 通过索引随机访问,时间复杂度O(1)
- 插入/删除元素可能需要移动其他元素
JS中的数组操作:
const arr = new Array(5).fill(0); // 创建长度为5的数组,初始值为0
arr.push(1); // 尾部插入,O(1)
arr.pop(); // 尾部删除,O(1)
arr.unshift(1); // 头部插入,O(n)
arr.shift(); // 头部删除,O(n)
arr.splice(2, 0, 5); // 在索引2处插入5,O(n)
3.1.2 链表(Linked List)
特点:
- 非连续的内存空间
- 每个节点包含数据和指向下一个节点的指针
- 插入/删除效率高,但随机访问效率低
实现一个简单的链表:
class ListNode {
constructor(val) {
this.val = val;
this.next = null;
}
}
class LinkedList {
constructor() {
this.head = null;
this.size = 0;
}
add(val) {
const node = new ListNode(val);
if (!this.head) {
this.head = node;
} else {
let current = this.head;
while (current.next) {
current = current.next;
}
current.next = node;
}
this.size++;
}
}
3.1.3 栈(Stack)和队列(Queue)
栈(LIFO)实现:
class Stack {
constructor() {
this.items = [];
}
push(element) {
this.items.push(element);
}
pop() {
return this.items.pop();
}
peek() {
return this.items[this.items.length - 1];
}
isEmpty() {
return this.items.length === 0;
}
}
队列(FIFO)实现:
class Queue {
constructor() {
this.items = [];
}
enqueue(element) {
this.items.push(element);
}
dequeue() {
return this.items.shift();
}
front() {
return this.items[0];
}
isEmpty() {
return this.items.length === 0;
}
}
3.2 非线性结构
- 树:层级关系(DOM树、文件系统)
- 图:网络关系(社交网络、路由算法)
3.2.1 树(Tree)
二叉树实现:
class TreeNode {
constructor(val) {
this.val = val;
this.left = null;
this.right = null;
}
}
class BinaryTree {
constructor() {
this.root = null;
}
// 插入方法
insert(val) {
const newNode = new TreeNode(val);
if (!this.root) {
this.root = newNode;
return;
}
let current = this.root;
while (true) {
if (val < current.val) {
if (!current.left) {
current.left = newNode;
break;
}
current = current.left;
} else {
if (!current.right) {
current.right = newNode;
break;
}
current = current.right;
}
}
}
}
3.2.2 图(Graph)
邻接表表示法:
class Graph {
constructor() {
this.adjacencyList = {};
}
addVertex(vertex) {
if (!this.adjacencyList[vertex]) {
this.adjacencyList[vertex] = [];
}
}
addEdge(v1, v2) {
this.adjacencyList[v1].push(v2);
this.adjacencyList[v2].push(v1);
}
}
📌 4. JS实现数据结构技巧
4.1 数组的高级玩法
// 初始化技巧
const arr = new Array(10); // 长度10的空数组
const brr = Array(10).fill(1); // 10个1的数组
// 遍历性能对比(实测数据):
普通for循环 > forEach > map
4.2 手写栈和队列
// 用数组实现栈
class Stack {
constructor() {
this.items = [];
}
push(element) {
this.items.push(element);
}
pop() {
return this.items.pop();
}
}
// 用数组实现队列
class Queue {
constructor() {
this.items = [];
}
enqueue(element) {
this.items.push(element);
}
dequeue() {
return this.items.shift();
}
}
📌 5. 算法实战:排序算法对比
5.1 冒泡排序 vs 快速排序
| 算法 | 时间复杂度 | 空间复杂度 | 稳定性 |
|---|---|---|---|
| 冒泡排序 | O(n²) | O(1) | 稳定 |
| 快速排序 | O(nlogn) | O(logn) | 不稳定 |
// 快速排序实现
function quickSort(arr) {
if (arr.length <= 1) return arr;
const pivot = arr[0];
const left = [];
const right = [];
for (let i = 1; i < arr.length; i++) {
arr[i] < pivot ? left.push(arr[i]) : right.push(arr[i]);
}
return [...quickSort(left), pivot, ...quickSort(right)];
}
📌 6. 大厂面试避坑指南
6.1 常见误区
- 只刷题不总结 → 建立错题本
- 死记硬背 → 理解算法思想
- 忽视边界条件 → 多写测试用例
6.2 面试黄金法则
- 先clarify问题
- 说思路再写代码
- 分析复杂度
- 讨论优化空间
🎯 最后的话
算法学习就像打游戏升级:
- 新手村(基本数据结构)
- 中级副本(经典算法)
- 终极Boss(动态规划、图论)
互动环节: 👉 你在学习算法时遇到过哪些困难? 👉 你最喜欢的算法是什么?为什么? 👉 对本文内容有任何疑问,欢迎在评论区留言讨论!
我是算法探险家FogLetter,关注我,带你轻松拿下大厂Offer!🚀
如果觉得这篇文章有帮助,别忘了点赞收藏哦!我们下期再见!