数据结构与算法|第二十四章:算法思维总结与实战

数据结构与算法|算法思维总结与实战

  • [第二十四章 算法思维总结与实战](#第二十四章 算法思维总结与实战)
    • [24.1 十大算法策略回顾](#24.1 十大算法策略回顾)
      • [24.1.1 分治(Divide and Conquer)](#24.1.1 分治(Divide and Conquer))
      • [24.1.2 贪心(Greedy)](#24.1.2 贪心(Greedy))
      • [24.1.3 动态规划(Dynamic Programming)](#24.1.3 动态规划(Dynamic Programming))
        • [1. 定义状态](#1. 定义状态)
        • [2. 推导状态转移方程](#2. 推导状态转移方程)
        • [3. 确定边界条件](#3. 确定边界条件)
        • [4. 确定计算顺序](#4. 确定计算顺序)
      • [24.1.4 回溯(Backtracking)](#24.1.4 回溯(Backtracking))
      • [24.1.5 搜索(Search)](#24.1.5 搜索(Search))
      • [24.1.6 枚举(Enumeration)](#24.1.6 枚举(Enumeration))
      • [24.1.7 模拟(Simulation)](#24.1.7 模拟(Simulation))
      • [24.1.8 位运算(Bit Manipulation)](#24.1.8 位运算(Bit Manipulation))
      • [24.1.9 双指针(Two Pointers)](#24.1.9 双指针(Two Pointers))
      • [24.1.10 滑动窗口(Sliding Window)](#24.1.10 滑动窗口(Sliding Window))
    • [24.2 十大策略横向对比与选择指南](#24.2 十大策略横向对比与选择指南)
    • [24.3 刷题方法论:如何高效 LeetCode](#24.3 刷题方法论:如何高效 LeetCode)
      • [24.3.1 刷题的正确姿势](#24.3.1 刷题的正确姿势)
        • [1. 按专题刷,不要随机刷](#1. 按专题刷,不要随机刷)
        • [2. 一题多解,追求深度](#2. 一题多解,追求深度)
        • [3. 定期复盘,建立知识体系](#3. 定期复盘,建立知识体系)
      • [24.3.2 刷题节奏规划](#24.3.2 刷题节奏规划)
      • [24.3.3 解题思维框架](#24.3.3 解题思维框架)
    • [24.4 设计模式中的数据结构](#24.4 设计模式中的数据结构)
      • [24.4.1 观察者模式与发布-订阅](#24.4.1 观察者模式与发布-订阅)
      • [24.4.2 责任链模式](#24.4.2 责任链模式)
      • [24.4.3 策略模式](#24.4.3 策略模式)
      • [24.4.4 设计模式与数据结构对应总结](#24.4.4 设计模式与数据结构对应总结)
    • [24.5 真实工程场景中的算法应用](#24.5 真实工程场景中的算法应用)
      • [24.5.1 搜索引擎------倒排索引与 TF-IDF](#24.5.1 搜索引擎——倒排索引与 TF-IDF)
      • [24.5.2 推荐系统------协同过滤](#24.5.2 推荐系统——协同过滤)
      • [24.5.3 分布式系统------一致性哈希](#24.5.3 分布式系统——一致性哈希)
      • [24.5.4 工程应用总结](#24.5.4 工程应用总结)
    • [24.6 算法与架构设计的关系](#24.6 算法与架构设计的关系)
      • [24.6.1 数据结构选择影响架构](#24.6.1 数据结构选择影响架构)
      • [24.6.2 算法思维在架构中的应用](#24.6.2 算法思维在架构中的应用)
    • [24.7 大厂面试算法题高频考点总结](#24.7 大厂面试算法题高频考点总结)
      • [24.7.1 高频数据结构考点](#24.7.1 高频数据结构考点)
      • [24.7.2 高频算法策略考点](#24.7.2 高频算法策略考点)
      • [24.7.3 大厂面试高频 Top 20 题目](#24.7.3 大厂面试高频 Top 20 题目)
      • [24.7.4 面试算法题解题模板](#24.7.4 面试算法题解题模板)
    • [24.8 本系列全篇知识图谱](#24.8 本系列全篇知识图谱)
    • [24.9 总结](#24.9 总结)
      • [24.9.1 核心回顾](#24.9.1 核心回顾)
      • [24.9.2 学习算法的三个境界](#24.9.2 学习算法的三个境界)

上篇:第二十三章、高级数据结构

第二十四章 算法思维总结与实战

历经二十三章的学习,我们从线性结构一路走到高级数据结构,从递归分治讲到动态规划与回溯。本章是整个系列的收官之作------不是终点,而是将所有知识点串联起来的枢纽。我们将系统回顾十大算法策略,梳理刷题方法论,探讨算法在工程中的真实应用,并总结大厂面试的高频考点。


24.1 十大算法策略回顾

在数据结构与算法的世界中,解决问题的方式是有"套路"可循的。以下十大策略几乎覆盖了所有常见算法题的核心思路。
十大算法策略
分治
贪心
动态规划
回溯
搜索
枚举
模拟
位运算
双指针
滑动窗口

24.1.1 分治(Divide and Conquer)

分治策略:将原问题分解为若干个规模较小、相互独立、与原问题形式相同的子问题,递归求解子问题,最后合并子问题的解得到原问题的解。

三步走框架:

复制代码
分治(问题 P):
    if P 的规模足够小:
        直接求解并返回
    将 P 分解为若干子问题 P1, P2, ..., Pk
    for each Pi:
        递归求解 Pi 的解 Si
    合并 S1, S2, ..., Sk 得到 P 的解
    返回 P 的解

典型应用:

问题 分解方式 合并方式 时间复杂度
归并排序 从中间一分为二 两个有序数组合并 O(n log n)
快速排序 以基准元素分为两部分 原地拼接 O(n log n)
最大子数组和 按中点分为左右两部分 跨中点的最大和 O(n log n)
汉诺塔 分为 n-1 个盘和 1 个盘 按规则移动 O(2ⁿ)

核心要点:分治的效率取决于子问题之间是否独立。如果子问题重叠,应考虑动态规划。

24.1.2 贪心(Greedy)

贪心策略:在每一步选择中都采取当前状态下的最优选择(局部最优),期望通过一系列局部最优选择达到全局最优。

贪心成立的两个条件:

  1. 贪心选择性质:局部最优选择能导致全局最优解
  2. 最优子结构:问题的最优解包含子问题的最优解

贪心 vs 动态规划:

对比维度 贪心算法 动态规划
决策方式 每步做局部最优选择 考虑所有可能的子问题解
是否回溯 不回溯,一旦选择不可更改 可以回溯,通过状态转移重新选择
适用条件 贪心选择性质 + 最优子结构 最优子结构 + 重叠子问题
时间效率 通常更快(O(n) ~ O(n log n)) 相对较慢(O(n²) ~ O(n³))
解的保证 不一定最优 一定最优

典型应用:

  • 活动选择问题:按结束时间排序,每次选最早结束的活动
  • 分数背包问题:按单位价值排序,优先选单位价值最高的物品
  • 哈夫曼编码:每次合并频率最小的两棵树
  • 跳跃游戏:每步维护能到达的最远位置

24.1.3 动态规划(Dynamic Programming)

动态规划策略:将复杂问题分解为重叠子问题,通过存储已求解的子问题结果避免重复计算,从而大幅降低时间复杂度。

DP 解题四步法:

1. 定义状态

明确 dp[i]dp[i][j] 的含义------这是解题的关键一步。

2. 推导状态转移方程

找出状态之间的递推关系。

3. 确定边界条件

基础情况的初始值。

4. 确定计算顺序

自底向上(递推)或自顶向下(记忆化搜索)。

经典 DP 模型:

模型 代表问题 状态定义 转移方程
线性 DP 最长递增子序列 dp[i]:以 i 结尾的 LIS 长度 dp[i] = max(dp[j] + 1)
区间 DP 矩阵链乘法 dp[i][j]:区间 [i,j] 的最小代价 dp[i][j] = min(dp[i][k] + dp[k+1][j] + cost)
背包 DP 0-1 背包 dp[i][w]:前 i 个物品容量 w 的最大价值 dp[i][w] = max(dp[i-1][w], dp[i-1][w-wi] + vi)
树形 DP 打家劫舍 III dp[node][0/1]:选/不选当前节点 递推依赖子节点状态
状态压缩 DP 旅行商问题 dp[mask][i]:经过集合 mask 到达 i 的最短路 dp[mask][i] = min(dp[mask ^ (1<<i)][j] + dist[j][i])

核心要点:DP 的难点在于状态定义和状态转移方程的推导。建议从暴力递归出发,发现重叠子问题后加备忘录优化,最后转为递推形式。

24.1.4 回溯(Backtracking)

回溯策略:在问题的解空间中按深度优先的方式进行搜索,当发现当前路径不可能得到有效解时,回退到上一个节点尝试其他选择。

回溯解题框架:

java 复制代码
void backtrack(路径, 选择列表) {
    if (满足结束条件) {
        记录结果;
        return;
    }
    for (选择 : 选择列表) {
        做选择;
        backtrack(新路径, 新选择列表);
        撤销选择;  // 回溯
    }
}

回溯的核心------剪枝:

剪枝是回溯效率的关键,常见的剪枝策略:

剪枝类型 说明 示例
排序剪枝 先排序,跳过不可能的分支 组合总和(排序后跳过大于 target 的数)
去重剪枝 跳过重复元素避免重复解 全排列 II(same level 去重)
约束剪枝 不满足约束条件直接跳过 N 皇后(同列/对角线冲突)
限界剪枝 当前解不可能优于已知最优解 分配问题(剩余最小值仍超当前最优)

24.1.5 搜索(Search)

搜索策略:在状态空间中系统地寻找目标状态,主要包括 DFS(深度优先搜索)和 BFS(广度优先搜索)。

DFS vs BFS 对比:

对比维度 DFS BFS
数据结构 栈(递归调用栈) 队列
空间复杂度 O(h),h 为深度 O(w),w 为宽度
最短路径 不保证 无权图保证最短路径
适用场景 连通性判断、拓扑排序、所有解 最短路径、层序遍历、最少步数

搜索的变种:

  • 双向 BFS:从起点和终点同时搜索,当两边相遇时找到最短路径
  • A 搜索*:使用启发式函数评估节点的优先级,优先搜索更有希望的节点
  • 迭代加深 DFS(IDDFS):限制 DFS 深度,逐步加深,兼有 DFS 的空间效率和 BFS 的完备性

24.1.6 枚举(Enumeration)

枚举策略:遍历问题的所有可能解空间,逐一验证是否满足条件。看似"暴力",但在某些场景下是唯一可行的方案。

枚举的优化思路:

优化方式 说明 效果
缩小范围 通过分析减少枚举范围 O(n²) → O(n)
提前终止 不满足条件时立即跳出 减少无效计算
对称性剪枝 利用问题的对称性减半枚举 时间减半
状态压缩 用位运算表示状态减少枚举量 O(2ⁿ) → O(2ⁿ/k)

典型应用: 两数之和(暴力枚举 O(n²))、子集枚举(位掩码)、暴力破解密码

24.1.7 模拟(Simulation)

模拟策略:按照题目描述的规则,一步一步地模拟过程,不需要复杂的数学推导或优化。

模拟的核心能力:

  1. 读懂题意:准确理解每一步操作规则
  2. 边界处理:处理好初始状态和终止条件
  3. 代码实现:将自然语言描述精确翻译为代码

典型应用: 螺旋矩阵、旋转图像、Z 字形变换、生命游戏

注意 :模拟题不考察算法设计能力,但考察代码实现能力细节处理能力。面试中往往作为第一道题出现。

24.1.8 位运算(Bit Manipulation)

位运算策略:利用二进制位的操作来优化计算,常用于集合表示、状态压缩和特定数学运算。

常用位运算技巧:

操作 位运算写法 说明
判断奇偶 (n & 1) == 1 最低位为 1 则奇数
交换两数 a ^= b; b ^= a; a ^= b; 不用临时变量
乘以 2 的 k 次方 n << k 等价于 n * 2ᵏ
除以 2 的 k 次方 n >> k 等价于 n / 2ᵏ
取最低位 1 n & (-n) lowbit 操作
消去最低位 1 n & (n - 1) 统计 1 的个数
判断 2 的幂 (n & (n - 1)) == 0 只有一位为 1

典型应用: 只出现一次的数字、子集枚举、两整数之和、位图(BitMap)

24.1.9 双指针(Two Pointers)

双指针策略:使用两个指针在数据结构上进行协同遍历,常见于数组、链表和字符串问题。

三种常见模式:

模式 初始位置 移动方向 适用场景
对撞指针 左右两端 向中间移动 有序数组的两数之和、回文判断、容器盛水
快慢指针 同一起点 速度不同 链表环检测、链表中点、删除倒数第 N 个节点
滑动窗口 同一起点 一前一后 子串匹配、最长/最短子数组

典型应用:

  • 有序数组的两数之和:对撞指针 O(n)
  • 盛最多水的容器:对撞指针,短边移动
  • 删除有序数组的重复项:快慢指针
  • 三数之和:排序 + 对撞指针

24.1.10 滑动窗口(Sliding Window)

滑动窗口策略:在数组或字符串上维护一个动态窗口,通过窗口的滑动来求解问题,是双指针的高级应用。

滑动窗口框架:

java 复制代码
public String minWindow(String s, String t) {
    // 1. 初始化窗口和计数器
    int[] need = new int[128];
    int[] window = new int[128];
    for (char c : t.toCharArray()) need[c]++;

    int left = 0, right = 0;
    int valid = 0;     // 窗口中满足条件的字符种类数
    int start = 0, minLen = Integer.MAX_VALUE;

    // 2. 扩大窗口
    while (right < s.length()) {
        char c = s.charAt(right);
        right++;
        if (need[c] > 0) {
            window[c]++;
            if (window[c] == need[c]) valid++;
        }

        // 3. 缩小窗口
        while (valid == countUniqueChars(t)) {
            if (right - left < minLen) {
                start = left;
                minLen = right - left;
            }
            char d = s.charAt(left);
            left++;
            if (need[d] > 0) {
                if (window[d] == need[d]) valid--;
                window[d]--;
            }
        }
    }
    return minLen == Integer.MAX_VALUE ? "" : s.substring(start, start + minLen);
}

典型应用: 最小覆盖子串、无重复字符的最长子串、找到字符串中所有字母异位词、长度最小的子数组


24.2 十大策略横向对比与选择指南

面对一道算法题,如何快速选择合适的策略?以下对比表提供了决策参考。

策略 核心思想 适用特征 时间复杂度量级 代表题型
分治 分解 → 解决 → 合并 可独立分解的子问题 O(n log n) 排序、最近点对
贪心 局部最优 → 全局最优 存在贪心选择性质 O(n) ~ O(n log n) 调度、霍夫曼
动态规划 状态转移 → 最优子结构 重叠子问题 O(n²) ~ O(n³) 背包、LCS、LIS
回溯 DFS + 剪枝 求所有解/可行解 O(2ⁿ) 或 O(n!) 排列组合、N 皇后
搜索 状态空间遍历 图/网格上的路径/连通性 O(V + E) 迷宫、最短路径
枚举 遍历所有可能 解空间较小或无规律 依问题而定 暴力破解、穷举
模拟 按规则执行 规则明确的过程 依问题而定 矩阵操作、游戏规则
位运算 二进制操作 与位状态相关 O(1) ~ O(n) 状态压缩、去重
双指针 协同遍历 有序/线性结构 O(n) 两数之和、去重
滑动窗口 动态窗口 连续子串/子数组 O(n) 子串匹配、最值

选择策略的决策树

  1. 问题是否有最优子结构?→ 是:考虑 DP 或贪心
  2. 子问题是否重叠?→ 是:用 DP;否:考虑分治
  3. 是否要求所有解?→ 是:回溯
  4. 问题在图/网格上?→ 搜索(DFS/BFS)
  5. 数据是有序/线性的?→ 双指针或滑动窗口
  6. 规则明确但无明显优化?→ 模拟或枚举
  7. 涉及位状态/集合?→ 位运算

24.3 刷题方法论:如何高效 LeetCode

24.3.1 刷题的正确姿势

很多人刷了几百道题依然感觉没有进步,根本原因在于刷题方式不对。以下是一套经过验证的高效方法论。

1. 按专题刷,不要随机刷

按数据结构和算法策略分类刷题,同一专题连续做 5-10 道,形成模式识别能力。

推荐刷题顺序:
数组与字符串
链表
栈与队列
哈希表

二分查找

回溯
动态规划
贪心
高级专题

2. 一题多解,追求深度

同一道题至少尝试两种解法,比较时间/空间复杂度的差异:

java 复制代码
// 示例:反转链表 ------ 两种经典解法

// 方法一:迭代法(推荐,空间 O(1))
public ListNode reverseListIterative(ListNode head) {
    ListNode prev = null;
    ListNode cur = head;
    while (cur != null) {
        ListNode next = cur.next;
        cur.next = prev;
        prev = cur;
        cur = next;
    }
    return prev;
}

// 方法二:递归法(优雅但空间 O(n))
public ListNode reverseListRecursive(ListNode head) {
    if (head == null || head.next == null) {
        return head;
    }
    ListNode newHead = reverseListRecursive(head.next);
    head.next.next = head;
    head.next = null;
    return newHead;
}
3. 定期复盘,建立知识体系

每刷完一个专题,写一篇总结:

  • 这个专题的核心思路是什么?
  • 有哪些常见变体
  • 遇到类似题目如何快速识别

24.3.2 刷题节奏规划

阶段 题量 重点 每日建议
入门期(1-2 月) 100-150 题 Easy 为主,理解基本数据结构 2-3 题/天
进阶期(3-4 月) 200-300 题 Easy + Medium,掌握核心算法策略 2 题/天
冲刺期(1-2 月) 50-100 题 Medium + Hard,专题突破 1-2 题/天
面试期 持续 高频题反复练,模拟面试 每日 1 次模拟

24.3.3 解题思维框架

面对一道新题,按照以下步骤思考:

复制代码
1. 审题(2 分钟)
   - 输入是什么?输出是什么?
   - 数据规模多大?(决定算法复杂度上限)
   - 有无特殊条件?(有序、无重复、范围限制等)

2. 思考(5-10 分钟)
   - 属于哪个专题?(数据结构 or 算法策略)
   - 能否套用已知模板?
   - 有没有更优的解法?

3. 编码(15-20 分钟)
   - 先写核心逻辑,再处理边界
   - 变量命名清晰,逻辑简洁

4. 验证(5 分钟)
   - 正常用例
   - 边界用例(空输入、单元素、极端值)
   - 分析时间/空间复杂度

5. 优化(5 分钟)
   - 有无冗余计算?
   - 能否空间换时间 or 时间换空间?

关键原则 :如果 20 分钟内完全没有思路,直接看题解。看题解不是偷懒,而是学习新的思维模式。但看完题解后,必须关掉答案自己手写一遍。


24.4 设计模式中的数据结构

设计模式是软件工程中的经典方案,而数据结构是设计模式的底层支撑。理解数据结构在设计模式中的角色,能帮助我们更深刻地理解两者的关联。

24.4.1 观察者模式与发布-订阅

核心数据结构:哈希表 + 链表

观察者模式维护一个"主题 → 观察者列表"的映射关系,天然需要哈希表来快速查找主题,链表来存储观察者。

java 复制代码
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;

/**
 * 观察者模式 ------ 事件总线实现
 * 底层数据结构:HashMap(事件类型 → 观察者链表)
 */
public class EventBus {

    private final Map<Class<?>, List<Observer<?>>> observerMap = new HashMap<>();

    /**
     * 注册观察者:HashMap.put + LinkedList.add
     */
    public <T> void register(Class<T> eventType, Observer<T> observer) {
        observerMap.computeIfAbsent(eventType, k -> new ArrayList<>()).add(observer);
    }

    /**
     * 取消注册:LinkedList.remove
     */
    public <T> void unregister(Class<T> eventType, Observer<T> observer) {
        List<Observer<?>> observers = observerMap.get(eventType);
        if (observers != null) {
            observers.remove(observer);
        }
    }

    /**
     * 发布事件:HashMap.get → 遍历链表通知
     */
    @SuppressWarnings("unchecked")
    public <T> void post(T event) {
        List<Observer<?>> observers = observerMap.get(event.getClass());
        if (observers != null) {
            for (Observer<?> observer : observers) {
                ((Observer<T>) observer).onEvent(event);
            }
        }
    }

    public interface Observer<T> {
        void onEvent(T event);
    }
}

24.4.2 责任链模式

核心数据结构:链表

责任链模式将请求沿着处理者链传递,每个处理者决定是否处理或传递给下一个,天然是链表结构。

java 复制代码
/**
 * 责任链模式 ------ 请求处理器链
 * 底层数据结构:单向链表
 */
public abstract class Handler {

    private Handler next;

    /**
     * 设置下一个处理者:链表 next 指针
     */
    public Handler setNext(Handler next) {
        this.next = next;
        return next;
    }

    /**
     * 处理请求:链表遍历
     */
    public abstract void handleRequest(Request request);

    /**
     * 传递给下一个处理者
     */
    protected void passToNext(Request request) {
        if (next != null) {
            next.handleRequest(request);
        }
    }

    public static class Request {
        private final String type;
        private final String data;

        public Request(String type, String data) {
            this.type = type;
            this.data = data;
        }

        public String getType() { return type; }
        public String getData() { return data; }
    }
}

24.4.3 策略模式

核心数据结构:哈希表(策略注册表)

策略模式通过将算法封装为独立的策略对象,使用哈希表来管理策略的注册与查找。

java 复制代码
import java.util.Map;
import java.util.HashMap;

/**
 * 策略模式 ------ 支付策略工厂
 * 底层数据结构:HashMap(策略名 → 策略实例)
 */
public class PaymentStrategyFactory {

    private final Map<String, PaymentStrategy> strategyMap = new HashMap<>();

    /**
     * 注册策略:HashMap.put
     */
    public void register(String type, PaymentStrategy strategy) {
        strategyMap.put(type, strategy);
    }

    /**
     * 获取策略:HashMap.get (O(1))
     */
    public PaymentStrategy getStrategy(String type) {
        PaymentStrategy strategy = strategyMap.get(type);
        if (strategy == null) {
            throw new IllegalArgumentException("未知的支付方式: " + type);
        }
        return strategy;
    }

    public interface PaymentStrategy {
        void pay(int amount);
    }
}

24.4.4 设计模式与数据结构对应总结

设计模式 核心数据结构 数据结构的作用
观察者模式 哈希表 + 链表 主题到观察者的映射与存储
责任链模式 链表 处理者链的串联与传递
策略模式 哈希表 策略名到策略对象的映射
迭代器模式 栈/队列 遍历复杂数据结构(如树的迭代遍历)
享元模式 哈希表 共享对象的缓存池
命令模式 队列/栈 命令的排队执行与撤销重做
状态模式 状态机(图) 状态转换的有限自动机
组合模式 部分-整体的层次结构

24.5 真实工程场景中的算法应用

算法不只是面试题,在真实的工程场景中无处不在。以下是几个典型案例。

24.5.1 搜索引擎------倒排索引与 TF-IDF

搜索引擎的核心是倒排索引 ,本质是一个 哈希表(词 → 文档链表) 的结构。

java 复制代码
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import java.util.HashSet;

/**
 * 倒排索引简化实现
 * 底层数据结构:HashMap(关键词 → 文档ID列表)
 */
public class InvertedIndex {

    private final Map<String, List<Integer>> index = new HashMap<>();
    private int docCount = 0;

    /**
     * 添加文档:分词 → HashMap.put
     */
    public int addDocument(String[] keywords) {
        int docId = docCount++;
        for (String keyword : keywords) {
            index.computeIfAbsent(keyword, k -> new ArrayList<>()).add(docId);
        }
        return docId;
    }

    /**
     * 单词搜索:HashMap.get → O(1)
     */
    public List<Integer> search(String keyword) {
        return index.getOrDefault(keyword, new ArrayList<>());
    }

    /**
     * 多词 AND 搜索:多个有序链表求交集(双指针)
     */
    public List<Integer> searchAnd(String keyword1, String keyword2) {
        List<Integer> list1 = search(keyword1);
        List<Integer> list2 = search(keyword2);
        List<Integer> result = new ArrayList<>();
        int i = 0, j = 0;
        while (i < list1.size() && j < list2.size()) {
            if (list1.get(i).equals(list2.get(j))) {
                result.add(list1.get(i));
                i++; j++;
            } else if (list1.get(i) < list2.get(j)) {
                i++;
            } else {
                j++;
            }
        }
        return result;
    }

    /**
     * 计算 TF-IDF(词频-逆文档频率)
     * TF = 词在文档中出现的次数 / 文档总词数
     * IDF = log(总文档数 / 包含该词的文档数)
     */
    public double tfIdf(String keyword, int docId, int totalWordsInDoc) {
        List<Integer> docList = search(keyword);
        int tf = 0;
        for (int id : docList) {
            if (id == docId) tf++;
        }
        double idf = Math.log((double) docCount / (docList.size() + 1));
        return ((double) tf / totalWordsInDoc) * idf;
    }
}

算法要点 :倒排索引用哈希表 实现 O(1) 的词查找;多词搜索用双指针 合并有序链表;排序用实现 Top-K 结果。

24.5.2 推荐系统------协同过滤

推荐系统中最经典的算法之一是基于用户的协同过滤,核心是计算用户之间的相似度。

java 复制代码
import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import java.util.HashSet;

/**
 * 协同过滤简化实现
 * 核心算法:余弦相似度计算
 */
public class CollaborativeFiltering {

    // userId → (itemId → rating)
    private final Map<Integer, Map<Integer, Double>> userRatings = new HashMap<>();

    public void addUserRating(int userId, int itemId, double rating) {
        userRatings.computeIfAbsent(userId, k -> new HashMap<>()).put(itemId, rating);
    }

    /**
     * 计算两个用户的余弦相似度
     * cos(A, B) = (A·B) / (|A| × |B|)
     * 
     * 仅对两个用户共同评价过的物品计算
     */
    public double cosineSimilarity(int userA, int userB) {
        Map<Integer, Double> ratingsA = userRatings.get(userA);
        Map<Integer, Double> ratingsB = userRatings.get(userB);
        if (ratingsA == null || ratingsB == null) return 0.0;

        // 求交集:共同评价过的物品
        Set<Integer> commonItems = new HashSet<>(ratingsA.keySet());
        commonItems.retainAll(ratingsB.keySet());

        if (commonItems.isEmpty()) return 0.0;

        double dotProduct = 0.0, normA = 0.0, normB = 0.0;
        for (int item : commonItems) {
            double a = ratingsA.get(item);
            double b = ratingsB.get(item);
            dotProduct += a * b;
            normA += a * a;
            normB += b * b;
        }

        double denominator = Math.sqrt(normA) * Math.sqrt(normB);
        return denominator == 0 ? 0.0 : dotProduct / denominator;
    }

    /**
     * 为指定用户推荐物品
     * 找到最相似的 K 个用户,推荐他们喜欢但当前用户未评价的物品
     */
    public Map<Integer, Double> recommend(int userId, int topK) {
        Map<Integer, Double> similarities = new HashMap<>();
        for (int otherUser : userRatings.keySet()) {
            if (otherUser != userId) {
                similarities.put(otherUser, cosineSimilarity(userId, otherUser));
            }
        }

        // 用堆取 Top-K 相似用户
        Map<Integer, Double> recommendations = new HashMap<>();
        similarities.entrySet().stream()
                .sorted(Map.Entry.<Integer, Double>comparingByValue().reversed())
                .limit(topK)
                .forEach(entry -> {
                    Map<Integer, Double> otherRatings = userRatings.get(entry.getKey());
                    Map<Integer, Double> myRatings = userRatings.get(userId);
                    for (Map.Entry<Integer, Double> rating : otherRatings.entrySet()) {
                        if (!myRatings.containsKey(rating.getKey())) {
                            recommendations.merge(rating.getKey(),
                                    rating.getValue() * entry.getValue(), Double::sum);
                        }
                    }
                });

        return recommendations;
    }
}

算法要点 :协同过滤用哈希表 存储用户评分矩阵;用集合求交集 找共同评价;用取 Top-K 相似用户。

24.5.3 分布式系统------一致性哈希

一致性哈希是分布式缓存(如 Redis 集群)的核心算法,解决了节点增减时大量数据迁移的问题。

java 复制代码
import java.util.SortedMap;
import java.util.TreeMap;

/**
 * 一致性哈希简化实现
 * 底层数据结构:TreeMap(红黑树),保证有序性
 */
public class ConsistentHashing {

    private final TreeMap<Integer, String> ring = new TreeMap<>();
    private final int virtualNodeCount;

    public ConsistentHashing(int virtualNodeCount) {
        this.virtualNodeCount = virtualNodeCount;
    }

    /**
     * 添加节点:计算虚拟节点的哈希值,放入 TreeMap
     */
    public void addNode(String node) {
        for (int i = 0; i < virtualNodeCount; i++) {
            int hash = hash(node + "#VN" + i);
            ring.put(hash, node);
        }
    }

    /**
     * 移除节点
     */
    public void removeNode(String node) {
        for (int i = 0; i < virtualNodeCount; i++) {
            int hash = hash(node + "#VN" + i);
            ring.remove(hash);
        }
    }

    /**
     * 获取数据应该存放的节点
     * 核心:TreeMap.ceilingKey / higherKey → O(log n)
     */
    public String getNode(String key) {
        if (ring.isEmpty()) return null;
        int hash = hash(key);
        // 顺时针找第一个大于等于 hash 的节点
        SortedMap<Integer, String> tailMap = ring.tailMap(hash);
        int nodeHash = tailMap.isEmpty() ? ring.firstKey() : tailMap.firstKey();
        return ring.get(nodeHash);
    }

    /**
     * FNV1_32 哈希算法
     */
    private int hash(String key) {
        final int FNV_32_PRIME = 0x01000193;
        int hashValue = 0x811c9dc5;
        for (int i = 0; i < key.length(); i++) {
            hashValue ^= key.charAt(i);
            hashValue *= FNV_32_PRIME;
        }
        return hashValue;
    }
}

算法要点:一致性哈希用**红黑树(TreeMap)**维持哈希环的有序性,使得节点查找为 O(log n);虚拟节点解决数据倾斜问题。

24.5.4 工程应用总结

工程场景 核心数据结构 核心算法 时间复杂度
搜索引擎 倒排索引(哈希表 + 链表) TF-IDF、双指针合并 查询 O(1) + 合并 O(n)
推荐系统 评分矩阵(哈希表嵌套) 余弦相似度、Top-K 堆 相似度 O(n),推荐 O(n log K)
分布式缓存 一致性哈希环(红黑树) 一致性哈希 查找 O(log n)
消息队列 环形数组 / 堆 生产者-消费者、优先级调度 入队 O(1)/O(log n)
数据库索引 B+ 树 范围查询、顺序访问 查找 O(log n)

24.6 算法与架构设计的关系

24.6.1 数据结构选择影响架构

架构设计的核心是权衡,而数据结构的选择直接决定了系统的性能边界。

架构决策 数据结构选择 性能影响
缓存策略 LRU(HashMap + 双向链表) 淘汰 O(1)
路由表 前缀树(Trie) 最长前缀匹配 O(m)
消息去重 布隆过滤器 空间 O(n/8),可能误判
任务调度 优先队列(堆) 取最高优先级 O(log n)
限流器 滑动窗口计数器 判定 O(1)

24.6.2 算法思维在架构中的应用

架构设计中很多决策本质上是算法问题:

1. 分治思维 → 微服务拆分

将单体应用按业务边界分解为独立服务,每个服务独立开发、部署、扩展。这就是分治思想在架构层面的体现。

2. 缓存思维 → CDN + 多级缓存

用空间换时间,将热点数据放在离用户更近的位置。

3. 贪心思维 → 负载均衡

每次请求分配给当前负载最小的服务器,是一种贪心策略。

4. 回溯思维 → 重试与熔断

当一次请求失败,回退并尝试其他服务实例;当失败次数超阈值,熔断避免雪崩。

5. 动态规划思维 → 渐进式优化

不追求一次性最优,而是基于当前状态做出最优选择,逐步迭代到更好的架构。
算法思维
分治 → 微服务
缓存 → CDN/Redis
贪心 → 负载均衡
回溯 → 重试/熔断
DP → 渐进式优化
滑动窗口 → 限流


24.7 大厂面试算法题高频考点总结

24.7.1 高频数据结构考点

排名 数据结构 高频题数 典型题目
1 数组/字符串 ★★★★★ 两数之和、三数之和、最长子串
2 链表 ★★★★ 反转链表、合并有序链表、环检测
3 二叉树 ★★★★ 层序遍历、最大深度、路径总和
4 哈希表 ★★★★ 两数之和、字母异位词、LRU 缓存
5 栈/队列 ★★★ 有效的括号、每日温度、滑动窗口最大值
6 ★★★ Top K、合并 K 个链表、数据流中位数
7 ★★ 岛屿数量、课程表、网络延迟时间
8 Trie ★★ 单词搜索、前缀匹配

24.7.2 高频算法策略考点

排名 算法策略 高频题数 典型题目
1 动态规划 ★★★★★ 爬楼梯、打家劫舍、零钱兑换、LCS
2 双指针/滑动窗口 ★★★★ 两数之和、最小覆盖子串、接雨水
3 二分查找 ★★★★ 搜索旋转数组、寻找峰值、区间查找
4 BFS/DFS ★★★★ 岛屿数量、单词搜索、最短路径
5 回溯 ★★★ 全排列、N 皇后、组合总和
6 贪心 ★★★ 跳跃游戏、区间调度、分发糖果
7 分治 ★★ 排序、最大子数组和、搜索二维矩阵
8 位运算 ★★ 只出现一次的数字、子集枚举

24.7.3 大厂面试高频 Top 20 题目

以下是根据 LeetCode 频率和面试反馈整理的 Top 20 高频题:

排名 题目 难度 核心策略 数据结构
1 两数之和 Easy 哈希表 HashMap
2 反转链表 Easy 迭代/递归 链表
3 无重复字符的最长子串 Medium 滑动窗口 HashMap
4 二叉树层序遍历 Medium BFS 队列
5 合并两个有序链表 Easy 双指针 链表
6 有效的括号 Easy 栈匹配
7 最大子数组和 Medium DP / 分治 数组
8 三数之和 Medium 排序 + 双指针 数组
9 爬楼梯 Easy DP 数组
10 岛屿数量 Medium DFS/BFS 矩阵
11 LRU 缓存 Medium 哈希 + 双向链表 HashMap + LinkedList
12 接雨水 Hard 双指针/栈/DP 数组/栈
13 二叉树最大深度 Easy DFS/递归
14 搜索旋转排序数组 Medium 二分查找 数组
15 全排列 Medium 回溯 数组
16 零钱兑换 Medium DP 数组
17 打家劫舍 Medium DP 数组
18 合并 K 个升序链表 Hard 分治/堆 堆/链表
19 最长递增子序列 Medium DP / 二分 数组
20 课程表 Medium 拓扑排序 图 + 队列

24.7.4 面试算法题解题模板

以下是应对面试算法题的通用模板代码:

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;
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    return -1;
}

2. BFS 模板:

java 复制代码
import java.util.Queue;
import java.util.LinkedList;

public void bfs(int[][] grid, int startRow, int startCol) {
    int rows = grid.length, cols = grid[0].length;
    boolean[][] visited = new boolean[rows][cols];
    Queue<int[]> queue = new LinkedList<>();
    queue.offer(new int[]{startRow, startCol});
    visited[startRow][startCol] = true;

    int[][] directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};

    while (!queue.isEmpty()) {
        int[] curr = queue.poll();
        int r = curr[0], c = curr[1];
        for (int[] dir : directions) {
            int nr = r + dir[0], nc = c + dir[1];
            if (nr >= 0 && nr < rows && nc >= 0 && nc < cols
                    && !visited[nr][nc] && grid[nr][nc] == 1) {
                visited[nr][nc] = true;
                queue.offer(new int[]{nr, nc});
            }
        }
    }
}

3. DFS 模板:

java 复制代码
public void dfs(int[][] grid, int r, int c, boolean[][] visited) {
    int rows = grid.length, cols = grid[0].length;
    if (r < 0 || r >= rows || c < 0 || c >= cols
            || visited[r][c] || grid[r][c] == 0) {
        return;
    }
    visited[r][c] = true;
    dfs(grid, r - 1, c, visited);
    dfs(grid, r + 1, c, visited);
    dfs(grid, r, c - 1, visited);
    dfs(grid, r, c + 1, visited);
}

4. 回溯模板:

java 复制代码
import java.util.List;
import java.util.ArrayList;

public List<List<Integer>> subsets(int[] nums) {
    List<List<Integer>> result = new ArrayList<>();
    backtrack(nums, 0, new ArrayList<>(), result);
    return result;
}

private void backtrack(int[] nums, int start, List<Integer> path,
                       List<List<Integer>> result) {
    result.add(new ArrayList<>(path));
    for (int i = start; i < nums.length; i++) {
        path.add(nums[i]);
        backtrack(nums, i + 1, path, result);
        path.remove(path.size() - 1);
    }
}

5. DP 一维模板:

java 复制代码
public int climbStairs(int n) {
    if (n <= 2) return n;
    int prev2 = 1, prev1 = 2;
    for (int i = 3; i <= n; i++) {
        int curr = prev1 + prev2;
        prev2 = prev1;
        prev1 = curr;
    }
    return prev1;
}

24.8 本系列全篇知识图谱

二十四章的知识体系可以用以下图谱来概括:
数据结构与算法
基础篇
非线性结构篇
算法设计篇
高级专题篇
线性表
链表算法
数组与矩阵

队列
树-基础
树-进阶
堆与优先队列
哈希表
跳表

递归与分治
排序-比较类
排序-非比较类
二分查找
贪心
DP-基础
DP-进阶
回溯
字符串算法
位运算
高级数据结构
算法思维总结


24.9 总结

24.9.1 核心回顾

本章从十大算法策略出发,系统回顾了分治、贪心、DP、回溯、搜索、枚举、模拟、位运算、双指针和滑动窗口的核心思想与适用场景。然后深入探讨了刷题方法论、设计模式中的数据结构、工程场景中的算法应用、算法与架构的关系,最后总结了面试高频考点。

24.9.2 学习算法的三个境界

  1. 见山是山:理解每种数据结构和算法的基本原理,能看懂别人的代码
  2. 见山不是山:能识别问题背后的算法模式,将不同题目归类到对应的策略
  3. 见山还是山:融会贯通,面对新问题能灵活组合多种策略,甚至创造新方法

最后一句话 :算法学习没有捷径,但有方法。理解原理 → 动手实现 → 大量练习 → 总结复盘,这四个环节缺一不可。希望这个系列能成为你算法之路上的一块基石,而非终点。


全系列完结:至此,《数据结构与算法(Java 语言)》系列博客二十四章全部完成。从第一章绪论到本章的思维总结,我们系统学习了数据结构与算法的核心知识。未来的路上,持续练习、持续思考,算法思维将成为你解决一切复杂问题的利器。


上篇:第二十三章、高级数据结构

相关推荐
炽烈小老头1 小时前
【每天学习一点算法 2026/05/11】排序链表
学习·算法·链表
wefg11 小时前
一些零散的算法
c++·算法
khalil10201 小时前
代码随想录算法训练营Day-48 单调栈02 | 42. 接雨水、84.柱状图中最大的矩形
数据结构·c++·算法·leetcode·单调栈·接雨水
Hcoco_me1 小时前
Ai:Agent/ infra / 智驾 / 推广算法 题库
人工智能·深度学习·算法·自动驾驶·剪枝
项目申报小狂人1 小时前
提出了一种带双向搜索的粒子群优化算法,一种基于双四元数运动优化的新型无人机3D路径规划方法及应用
算法·3d·无人机
驼同学.1 小时前
牛客网面试TOP101 - Python算法学习指南
python·算法·面试
大大杰哥1 小时前
leetcode hot100(3)子串
c++·算法·leetcode
fish_xk1 小时前
哈希的了解
算法·哈希算法
水木流年追梦1 小时前
大模型入门-应用篇1-prompt技术
开发语言·python·算法·prompt