数据结构与算法实战指南

数据结构与算法实战指南

数据结构与算法是大厂面试的硬通货,也是 CSDN 流量最大的基础板块。本文不追求覆盖所有知识点,而是聚焦面试与实战最高频的部分:复杂度分析、数组链表、栈队列、哈希表、树与二叉树、排序、查找、动态规划、常见算法思想,每个都带图解式讲解和代码。


一、复杂度分析:算法的度量衡

1.1 时间复杂度

衡量算法执行时间随输入规模增长的趋势,用大 O 表示(忽略常数和低阶项):

复杂度 含义 例子
O(1) 常数时间 数组按下标取元素
O(log n) 对数时间 二分查找
O(n) 线性时间 遍历数组
O(n log n) 线性对数 快速排序、归并排序
O(n²) 平方时间 冒泡排序、双层循环
O(2ⁿ) 指数时间 斐波那契(朴素递归)

判断技巧:

  • 单层循环 → O(n);
  • 双层循环 → O(n²);
  • 循环减半 → O(log n);
  • 递归看递推式。

1.2 空间复杂度

算法运行需要多少额外内存:

  • O(1):几个变量;
  • O(n):一个长度为 n 的辅助数组。

面试要求:说解法时主动说"时间复杂度 O(n),空间复杂度 O(1)"。

1.3 实战:分析一道题

题目:判断一个字符串里的字符是否都不同。

python 复制代码
# 解法一:双重循环 O(n²),空间 O(1)
def is_unique(s):
    for i in range(len(s)):
        for j in range(i + 1, len(s)):
            if s[i] == s[j]:
                return False
    return True

# 解法二:哈希表 O(n),空间 O(n)
def is_unique(s):
    seen = set()
    for c in s:
        if c in seen:
            return False
        seen.add(c)
    return True

面试要能说出两种解法的复杂度差异。


二、数组与链表

2.1 数组(Array)

特点:

  • 内存连续,按下标访问 O(1);
  • 插入/删除要移动元素 O(n);
  • 扩容要整体搬移。

实战场景:随机访问多、元素个数确定。

2.2 链表(LinkedList)

特点:

  • 节点 + 指针,内存不连续;
  • 插入/删除(已知位置)O(1);
  • 按下标访问 O(n)。
java 复制代码
public class ListNode {
    int val;
    ListNode next;
    ListNode(int val) { this.val = val; }
}

常见操作:

java 复制代码
// 反转链表(面试高频)
public ListNode reverseList(ListNode head) {
    ListNode prev = null;
    ListNode curr = head;
    while (curr != null) {
        ListNode next = curr.next;
        curr.next = prev;
        prev = curr;
        curr = next;
    }
    return prev;
}

// 判断是否有环(快慢指针)
public boolean hasCycle(ListNode head) {
    ListNode slow = head, fast = head;
    while (fast != null && fast.next != null) {
        slow = slow.next;
        fast = fast.next.next;
        if (slow == fast) return true;
    }
    return false;
}

2.3 数组 vs 链表

维度 数组 链表
随机访问 O(1) O(n)
插入删除(头部) O(n) O(1)
内存 连续 分散
缓存友好 好 差

Java 里 :ArrayList 是数组,LinkedList 是链表。


三、栈与队列

3.1 栈(Stack):后进先出

场景:括号匹配、函数调用、撤销操作、表达式求值。

java 复制代码
// 括号匹配(经典题)
public boolean isValid(String s) {
    Deque<Character> stack = new ArrayDeque<>();
    Map<Character, Character> map = Map.of(')', '(', ']', '[', '}', '{');

    for (char c : s.toCharArray()) {
        if (map.containsKey(c)) {
            if (stack.isEmpty() || stack.pop() != map.get(c)) return false;
        } else {
            stack.push(c);
        }
    }
    return stack.isEmpty();
}

3.2 队列(Queue):先进先出

场景:任务排队、BFS 广度优先搜索、消息队列。

java 复制代码
Queue<Integer> queue = new LinkedList<>();
queue.offer(1);      // 入队
int head = queue.poll();   // 出队
queue.peek();        // 看队头

3.3 单调栈(进阶)

场景:下一个更大元素、柱状图最大矩形。

java 复制代码
// 下一个更大元素(O(n) 解法)
public int[] nextGreaterElement(int[] nums) {
    int n = nums.length;
    int[] result = new int[n];
    Arrays.fill(result, -1);
    Deque<Integer> stack = new ArrayDeque<>();   // 存下标

    for (int i = 0; i < n; i++) {
        while (!stack.isEmpty() && nums[stack.peek()] < nums[i]) {
            result[stack.pop()] = nums[i];
        }
        stack.push(i);
    }
    return result;
}

四、哈希表(HashMap)

4.1 原理

通过哈希函数把 key 映射到数组下标,查找 O(1)。冲突用链地址法(链表)或开放寻址。

Java HashMap 要点(面试必问):

  • 数组 + 链表 + 红黑树(链表超 8 个转红黑树);
  • 扩容:默认 16,负载因子 0.75,扩为 2 倍;
  • 为什么容量是 2 的幂:哈希 & (length-1) 快速取模。

4.2 实战:两数之和(LeetCode 1)

java 复制代码
public int[] twoSum(int[] nums, int target) {
    Map<Integer, Integer> map = new HashMap<>();
    for (int i = 0; i < nums.length; i++) {
        int complement = target - nums[i];
        if (map.containsKey(complement)) {
            return new int[]{map.get(complement), i};
        }
        map.put(nums[i], i);
    }
    return new int[]{-1, -1};
}

复杂度:O(n) 时间,O(n) 空间------典型的"空间换时间"。

4.3 实战:数组去重与计数

python 复制代码
from collections import Counter

# 统计词频
words = ["a", "b", "a", "c", "b", "a"]
counter = Counter(words)   # {'a': 3, 'b': 2, 'c': 1}
counter.most_common(2)     # [('a', 3), ('b', 2)]

五、树与二叉树

5.1 二叉树遍历(必考)

java 复制代码
public class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int val) { this.val = val; }
}

// 前序:根 → 左 → 右
public void preOrder(TreeNode node) {
    if (node == null) return;
    System.out.print(node.val + " ");
    preOrder(node.left);
    preOrder(node.right);
}

// 中序:左 → 根 → 右(二叉搜索树中序是升序)
public void inOrder(TreeNode node) {
    if (node == null) return;
    inOrder(node.left);
    System.out.print(node.val + " ");
    inOrder(node.right);
}

// 后序:左 → 右 → 根
public void postOrder(TreeNode node) {
    if (node == null) return;
    postOrder(node.left);
    postOrder(node.right);
    System.out.print(node.val + " ");
}

// 层序(BFS,用队列)
public void levelOrder(TreeNode root) {
    Queue<TreeNode> queue = new LinkedList<>();
    queue.offer(root);
    while (!queue.isEmpty()) {
        TreeNode node = queue.poll();
        System.out.print(node.val + " ");
        if (node.left != null) queue.offer(node.left);
        if (node.right != null) queue.offer(node.right);
    }
}

记忆口诀:前中后序看"根"的位置(前=根在前,中=根在中,后=根在后)。

5.2 二叉搜索树(BST)

性质:左子树 < 根 < 右子树。查找 O(log n)。

java 复制代码
public TreeNode searchBST(TreeNode root, int val) {
    if (root == null || root.val == val) return root;
    return val < root.val ? searchBST(root.left, val) : searchBST(root.right, val);
}

5.3 平衡树

BST 退化成链表就变 O(n)------需要自平衡:

  • AVL:严格平衡,旋转多;
  • 红黑树:近似平衡,Java TreeMap/TreeSet、HashMap 冲突链表用的就是它。

对比:MySQL 索引用 B+Tree(多叉,减少磁盘 IO);内存里用红黑树。


六、排序算法(面试必考)

6.1 快排(最常用)

python 复制代码
def quick_sort(arr):
    if len(arr) <= 1:
        return arr
    pivot = arr[len(arr) // 2]
    left  = [x for x in arr if x < pivot]
    mid   = [x for x in arr if x == pivot]
    right = [x for x in arr if x > pivot]
    return quick_sort(left) + mid + quick_sort(right)

复杂度 :平均 O(n log n),最坏 O(n²)(有序数组 + 选首元素)。不稳定。

工程优化:随机选 pivot、三数取中、小数组用插入排序。

6.2 归并排序(稳定)

python 复制代码
def merge_sort(arr):
    if len(arr) <= 1:
        return arr
    mid = len(arr) // 2
    left = merge_sort(arr[:mid])
    right = merge_sort(arr[mid:])
    return merge(left, right)

def merge(l, r):
    result = []
    i = j = 0
    while i < len(l) and j < len(r):
        if l[i] <= r[j]:
            result.append(l[i]); i += 1
        else:
            result.append(r[j]); j += 1
    return result + l[i:] + r[j:]

复杂度:O(n log n),稳定,空间 O(n)。

6.3 排序对比

算法 平均 最坏 稳定 空间
冒泡 O(n²) O(n²) 是 O(1)
插入 O(n²) O(n²) 是 O(1)
选择 O(n²) O(n²) 否 O(1)
快排 O(n log n) O(n²) 否 O(log n)
归并 O(n log n) O(n log n) 是 O(n)
堆排 O(n log n) O(n log n) 否 O(1)

工程结论:语言内置排序(Java Arrays.sort / Python sorted)都经过优化,直接调;面试考原理和手写快排/归并。


七、查找算法

7.1 二分查找(要求有序)

java 复制代码
public int binarySearch(int[] nums, int target) {
    int left = 0, right = nums.length - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;   // 防溢出
        if (nums[mid] == target) return mid;
        if (nums[mid] < target) left = mid + 1;
        else right = mid - 1;
    }
    return -1;
}

细节(面试踩坑点):

  • left <= right 还是 <:看区间定义;
  • mid = left + (right - left) / 2:防 (left+right) 溢出;
  • 变体:找左边界、右边界、第一个 ≥ target 的位置。

7.2 实战:旋转数组找最小值

java 复制代码
public int findMin(int[] nums) {
    int left = 0, right = nums.length - 1;
    while (left < right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] > nums[right]) left = mid + 1;
        else right = mid;
    }
    return nums[left];
}

八、动态规划(DP)

8.1 什么时候用 DP

  • 求最值(最大、最小、最少次数);
  • 有重叠子问题(递归会重复算);
  • 有最优子结构(子问题最优能推出整体最优)。

三要素:状态定义、转移方程、初始条件。

8.2 经典入门:斐波那契

python 复制代码
# 朴素递归:O(2^n),指数爆炸
def fib(n):
    if n <= 1: return n
    return fib(n-1) + fib(n-2)

# DP(自底向上):O(n)
def fib_dp(n):
    if n <= 1: return n
    dp = [0] * (n + 1)
    dp[1] = 1
    for i in range(2, n + 1):
        dp[i] = dp[i-1] + dp[i-2]
    return dp[n]

空间优化(滚动数组):只需要两个变量,O(1) 空间。

8.3 经典:爬楼梯(LeetCode 70)

问题:n 阶楼梯,每次爬 1 或 2 阶,有多少种爬法?

状态 :dp[i] = 爬到第 i 阶的方法数。

转移 :dp[i] = dp[i-1] + dp[i-2](最后一步爬 1 阶或 2 阶)。

初始 :dp[1]=1, dp[2]=2。

python 复制代码
def climb_stairs(n):
    if n <= 2: return n
    a, b = 1, 2
    for _ in range(3, n + 1):
        a, b = b, a + b
    return b

8.4 经典:最大子数组和(LeetCode 53)

问题:数组里连续子数组的最大和。

状态 :dp[i] = 以 numsi 结尾的最大子数组和。

转移 :dp[i] = max(nums[i], dp[i-1] + nums[i])(要么从自己开始,要么接前面)。

python 复制代码
def max_subarray(nums):
    max_sum = cur = nums[0]
    for x in nums[1:]:
        cur = max(x, cur + x)   # 状态转移
        max_sum = max(max_sum, cur)
    return max_sum

8.5 经典:0-1 背包

问题:容量 W 的背包,n 个物品(重量 wi,价值 vi),最多装多少价值?

状态 :dp[j] = 容量 j 能装的最大价值。

转移 :dp[j] = max(dp[j], dp[j-w[i]] + v[i])(逆序遍历防重复用)。

python 复制代码
def knapsack(W, weights, values):
    dp = [0] * (W + 1)
    for i in range(len(weights)):
        for j in range(W, weights[i] - 1, -1):   # 逆序!
            dp[j] = max(dp[j], dp[j - weights[i]] + values[i])
    return dp[W]

为什么逆序:正序会让同一个物品被多次放入(变成完全背包)。


九、算法思想总结

9.1 五大常用思想

思想 核心 典型
贪心 每步选当前最优 零钱兑换(某些)、区间调度
分治 拆小 → 解决 → 合并 归并排序、快排
回溯 试探 + 回退 全排列、N 皇后
DP 状态 + 转移 背包、最长公共子序列
双指针 一快一慢/左右夹逼 两数之和、链表判环

9.2 实战:双指针(有序数组两数之和)

python 复制代码
def two_sum_sorted(nums, target):
    left, right = 0, len(nums) - 1
    while left < right:
        s = nums[left] + nums[right]
        if s == target: return [left, right]
        if s < target: left += 1
        else: right -= 1
    return [-1, -1]

O(n) 时间,O(1) 空间------比哈希更省空间。


十、刷题路线建议

按顺序刷(LeetCode Hot 100):

  1. 数组/字符串(两数之和、盛水容器、最长子串);
  2. 链表(反转、环、合并);
  3. 树(遍历、最大深度、公共祖先);
  4. 二分/双指针;
  5. 栈队列(有效括号、滑动窗口);
  6. DP(爬楼梯、打家劫舍、背包);
  7. 图(BFS/DFS、岛屿数量)。

方法论:

  • 每道题先想暴力解,再说优化;
  • 会写代码 ≠ 会讲------面试要边说边写;
  • 高频题刷 3 遍(今天、三天后、一周后);
  • 归纳套路:看到"最值+可拆子问题"想 DP,看到"有序+查找"想二分。

本章小结

数据结构与算法的核心不是背题,是建立复杂度意识和套路库:数组链表选型、哈希空间换时间、树的分治思想、DP 的状态转移、双指针优化。把本文的十几道经典题吃透,再按路线刷 Hot 100,面试的算法关就稳了。

五篇 CSDN 热门主题文档全部完成:Vue3、PyTorch、Elasticsearch、设计模式、数据结构与算法------覆盖前端、AI、搜索、代码设计、算法五大方向。

(全文完)

相关推荐
Navigator_Z1 小时前
LeetCode //C - 1275. Find Winner on a Tic Tac Toe Game
c语言·算法·leetcode
未来之窗软件服务1 小时前
递归时间复杂度演算T (n)=aT (n/b)+f (n)-东方仙盟
算法·仙盟创梦ide·东方仙盟
垆边人似月.2 小时前
华为机考题:明明的随机数
数据结构·算法·华为
Doubbbbbbble云2 小时前
多维空间索引结构R树与KD树性能对比研究4
算法
海上小飞龙2 小时前
【KMP算法-中篇】next 数组的递推:往前填一位,靠的是往回退几步
算法
lusklusklusk2 小时前
Python 四大数据结构详解:元组、列表、集合、字典的特点与增删改查
数据结构·windows·python
keyipatience2 小时前
B树详解:从零构建平衡多路搜索树
数据结构·b树·算法·图搜索
FluxArt3 小时前
GPT Image 2.5 多图参考怎么用:Flux Art 生成与编辑教程
数据结构·gpt·算法
All for pursuit.3 小时前
【分治-2】215.数组中的第K个最大元素
数据结构·c++·算法·leetcode