资讯动态

排序、搜索与算法设计范式精讲:maths-cs-ai-compendium 第14章实战指南

发布时间:2026/9/17 13:48:52 来源:尧图企业网站定制
排序、搜索与算法设计范式精讲maths-cs-ai-compendium 第14章实战指南【免费下载链接】maths-cs-ai-compendiumBecome a cracked AI/ML researcher/engineer with this unconventional textbook covering maths, computing, and ML with intuition.项目地址: https://gitcode.com/GitHub_Trending/mat/maths-cs-ai-compendium排序与搜索是计算机科学中最基础的算法操作也是算法面试的高频主战场。本文以开源教科书Maths, CS AI Compendium的 第14章第05节「Sorting and Search」 为主体系统讲解七大经典排序算法的复杂度与稳定性、二分查找的四种进阶形态、以及贪心、动态规划、回溯三大设计范式的识别方法与可运行代码。读完本文你将掌握一套识别范式 → 套用模板 → 规避陷阱的完整解题方法论能够直接应对从 Easy 到 Hard 的排序搜索类面试题。每一类算法都会给出完整、可直接运行的 Python 实现、复杂度分析、最容易犯的边界错误以及配套的课后练习清单与本仓库 MCP 服务mcp/src/index.ts暴露的read_section检索能力呼应方便读者在本地仓库中随时回溯原文。排序算法复杂度、稳定性与比较排序下界排序是计算机科学中被研究得最透彻的问题之一。理解排序算法是建立递归、分治与复杂度分析直觉的最佳起点。下表汇总了七种经典排序算法在最好、平均、最坏三种情况下的时间复杂度和空间复杂度算法最好平均最坏空间稳定?冒泡排序$O(n)$$O(n^2)$$O(n^2)$$O(1)$是插入排序$O(n)$$O(n^2)$$O(n^2)$$O(1)$是归并排序$O(n \log n)$$O(n \log n)$$O(n \log n)$$O(n)$是快速排序$O(n \log n)$$O(n \log n)$$O(n^2)$$O(\log n)$否堆排序$O(n \log n)$$O(n \log n)$$O(n \log n)$$O(1)$否计数排序$O(n k)$$O(n k)$$O(n k)$$O(k)$是基数排序$O(d(n k))$$O(d(n k))$$O(d(n k))$$O(n k)$是稳定stable意味着相等元素的相对顺序在排序后保持不变。这在多关键字排序时至关重要例如先按姓名排序、再按分数排序若第二个排序不稳定第一关键字的顺序就会被破坏。归并排序、插入排序、计数排序、基数排序是稳定的快速排序与堆排序不稳定。比较排序的下界是 $\Omega(n \log n)$。证明思路用到决策树任何比较排序都必须能够区分所有 $n!$ 种排列因此至少需要 $\log_2(n!) \Omega(n \log n)$ 次比较。计数排序和基数排序之所以能突破这个下界正是因为它们不比较元素——而是利用键值的数值结构直接定位。这一下界分析依赖的离散数学与复杂度基础参见本仓库 第13章第01节「Discrete Maths」 与 第14章第00节「Foundations」。归并排序稳定的 $O(n \log n)$ 分治把数组对半切分递归排序两个子数组再合并两个有序半区。无论输入如何复杂度都是 $O(n \log n)$代价是需要 $O(n)$ 额外空间。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(left, right): result [] i j 0 while i len(left) and j len(right): if left[i] right[j]: # for stability result.append(left[i]) i 1 else: result.append(right[j]) j 1 result.extend(left[i:]) result.extend(right[j:]) return result关键陷阱合并时如果使用而非相等元素会来自右半区排在左半区之前破坏稳定性。保证相等时优先取左半区元素从而维持原数组的相对顺序。合并过程本身是两个有序序列的双指针归并这一技巧在后续双指针模式中还会反复出现。快速排序平均 $O(n \log n)$、最坏 $O(n^2)$ 的原地分治选取一个基准pivot把元素划分为小于基准和大于基准两个分区递归排序每个分区。平均 $O(n \log n)$当基准始终是最大或最小元素时退化为 $O(n^2)$。def quicksort(arr, lo0, hiNone): if hi is None: hi len(arr) - 1 if lo hi: return pivot_idx partition(arr, lo, hi) quicksort(arr, lo, pivot_idx - 1) quicksort(arr, pivot_idx 1, hi) def partition(arr, lo, hi): pivot arr[hi] # Lomuto: pivot is last element i lo for j in range(lo, hi): if arr[j] pivot: arr[i], arr[j] arr[j], arr[i] i 1 arr[i], arr[hi] arr[hi], arr[i] return i上面的partition使用Lomuto 分区方案以最后一个元素为基准i维护小于基准区域的边界j扫描剩余元素遇到小于基准的元素就与边界处交换。快排的最坏情况 $O(n^2)$ 出现在已排序数组配合首/尾基准时——此时每次分区都极度不均衡。基准选择策略取最后一个元素最简单但对已排序输入很差、随机选取期望复杂度 $O(n \log n)$、三数取中 median-of-three工程上最实用的选择。面试中建议优先讨论随机基准以避免最坏情况的纠缠。空间复杂度 $O(\log n)$ 来自递归栈深度这也是第14章第00节强调递归调用栈也要计入空间复杂度的典型例子。计数排序突破 $O(n \log n)$ 的非比较排序当所有值都是已知范围 $[0, k)$ 内的整数时统计每个值的出现次数再重建数组时间复杂度为 $O(n k)$。它不是基于比较的因此可以击败 $O(n \log n)$ 下界。def counting_sort(arr, k): count [0] * k for x in arr: count[x] 1 result [] for val in range(k): result.extend([val] * count[val]) return result适用时机当范围 $k$ 与 $n$ 同量级时$k O(n)$整体为线性 $O(n)$。若 $k \gg n$例如在 $[0, 10^9]$ 范围内排序 10 个数计数数组会浪费大量内存此时应改用比较排序。注意上面是不稳定的简化实现——如需稳定版本需要累积计数cumulative counts后从后向前放置元素。基数排序则对每一位执行稳定计数排序复杂度为 $O(d(n k))$其中 $d$ 是位数。工程视角语言内建排序通常远比手写排序精细。原文档的陷阱总结表明确提示使用稳定排序归并排序、Python 的sorted因此在多关键字排序等稳定性敏感的场景中优先依赖语言内建实现。模式一二分查找——在单调条件上搜索二分查找在有序数组中用 $O(\log n)$ 时间找到目标方法是反复将搜索空间减半。但二分查找远不止在有序数组中找一个数其通用模式是在单调条件monotonic condition上做搜索。标准模板规避 off-by-one 错误def binary_search(arr, target): lo, hi 0, len(arr) - 1 while lo hi: mid lo (hi - lo) // 2 # avoids overflow in other languages if arr[mid] target: return mid elif arr[mid] target: lo mid 1 else: hi mid - 1 return -1 # not foundmid lo (hi - lo) // 2而非(lo hi) // 2在 C/C/Java 中可避免lo hi的整数溢出Python 中无溢出风险但该写法作为习惯保留。下界第一个 $\geq$ target 的元素def lower_bound(arr, target): lo, hi 0, len(arr) while lo hi: mid (lo hi) // 2 if arr[mid] target: lo mid 1 else: hi mid return lo注意这里hi初始化为len(arr)右开区间循环条件是lo hi收缩时hi mid而非mid - 1。这正是陷阱所在lo hi与lo hi的区别、hi mid与hi mid - 1的区别决定了你找到的是精确匹配还是边界位置。拿一个只有 2 个元素的数组手动画一遍是最可靠的验证方式。lower_bound返回的索引可以直接作为 Cstd::lower_bound的语义arr[lo] target的第一个位置。Medium搜索旋转排序数组问题一个有序数组在某个支点处被旋转rotated在其中搜索目标值。模式每一步中总有一半是排序好的。判断哪一半有序再看目标是否落在该半区。def search_rotated(nums, target): lo, hi 0, len(nums) - 1 while lo hi: mid (lo hi) // 2 if nums[mid] target: return mid # left half is sorted if nums[lo] nums[mid]: if nums[lo] target nums[mid]: hi mid - 1 else: lo mid 1 # right half is sorted else: if nums[mid] target nums[hi]: lo mid 1 else: hi mid - 1 return -1关键陷阱nums[lo] nums[mid]中的而非至关重要。当区间只剩 2 个元素时lo mid必须用才能正确识别哪一半是有序的写成会把左半区已排序错误判定为右半区已排序。旋转数组的经典变形寻找旋转数组最小值也是同一思路二分寻找拐点。Hard两个有序数组的中位数问题在 $O(\log(m n))$ 时间内找到两个有序数组的中位数。模式对较短的数组二分搜索分割点。分割把两个数组都一分为二使得左侧所有元素都小于右侧所有元素。def find_median(nums1, nums2): if len(nums1) len(nums2): nums1, nums2 nums2, nums1 # ensure nums1 is shorter m, n len(nums1), len(nums2) lo, hi 0, m half (m n 1) // 2 while lo hi: i (lo hi) // 2 # partition point in nums1 j half - i # partition point in nums2 left1 nums1[i - 1] if i 0 else float(-inf) right1 nums1[i] if i m else float(inf) left2 nums2[j - 1] if j 0 else float(-inf) right2 nums2[j] if j n else float(inf) if left1 right2 and left2 right1: # correct partition if (m n) % 2 1: return max(left1, left2) return (max(left1, left2) min(right1, right2)) / 2 elif left1 right2: hi i - 1 else: lo i 1这是最难的二分查找问题之一。核心洞察是你搜索的不是某个值而是一个满足条件的分割点partition point。先在较短数组上枚举分割位置 $i$再通过j half - i推导另一个数组的分割位置用边界哨兵float(-inf)/float(inf)优雅处理越界情况。两个分割点共同满足left1 right2 and left2 right1时中位数即可由两侧最靠近分割点的四个数计算得出。元模式二分答案很多看起来与二分查找无关的问题可以通过对答案做二分解决。如果答案是某个数值 $x$并且你能写出一个单调的可行性判定函数is_feasible(x)对所有 $x \geq$ 最优值恒为 True或对所有 $x \geq$ 最优值恒为 False那么就可以对 $x$ 进行二分。经典例子一艘船至少需要多大的容量才能在 $d$ 天内运完所有包裹对容量做二分。对每个候选容量用贪心法检查是否能在 $d$ 天内运完def ship_within_days(weights, days): lo, hi max(weights), sum(weights) while lo hi: mid (lo hi) // 2 # can we ship with capacity mid in days? current_load, num_days 0, 1 for w in weights: if current_load w mid: num_days 1 current_load 0 current_load w if num_days days: hi mid else: lo mid 1 return lo判定函数单调性明显容量越大所需天数越少或不变。二分的下界是单件最大重量max(weights)容量必须装得下最重的包裹上界是总重量sum(weights)一天运完。判定函数内部本质是贪心装箱只要当前包裹放不进就新开一天。类似的二分答案题目还有 Koko 吃香蕉每小时吃 k 根能否在 h 小时内吃完等这类题在课后练习中会进一步巩固。模式二贪心算法——局部最优通往全局最优贪心算法在每一步做出当前看起来最优的选择希望由此得到全局最优解。贪心成立需要两个性质贪心选择性质greedy choice property局部最优选择能通向全局最优解最优子结构optimal substructure全局最优解包含子问题的最优解。贪心的最大陷阱是没有证明就用贪心——很多问题局部最优并不能导出全局最优典型反例见 动态规划一节 的硬币组合。下界问题都经过证明只要维护正确的局部状态贪心就是正确的。Medium跳跃游戏问题给定数组numsnums[i]是在位置 $i$ 能跳的最大长度判断能否到达最后一个下标。def can_jump(nums): max_reach 0 for i, jump in enumerate(nums): if i max_reach: return False # cannot reach this position max_reach max(max_reach, i jump) return True为什么贪心成立我们只需要知道最远可达位置。如果当前位置已经超出最远可达位置说明卡死了否则不断更新最远可达位置即可。不需要回溯、不需要 DP因为可达性具有单调扩张性质只要某个位置可达它之前的所有位置都可达。贪心的核心就是把状态压缩到最少必要信息——在这里就是最远可达位置这一个变量。Medium合并区间问题合并所有重叠的区间。def merge_intervals(intervals): intervals.sort(keylambda x: x[0]) merged [intervals[0]] for start, end in intervals[1:]: if start merged[-1][1]: merged[-1][1] max(merged[-1][1], end) else: merged.append([start, end]) return merged模式按开始时间排序然后贪心合并。如果当前区间与已合并的最后一个区间重叠就扩展它否则开启新的合并区间。这里排序是 $O(n \log n)$ 的预处理排序后单次线性扫描即可完成合并整体 $O(n \log n)$。关键陷阱合并时要用merged[-1][1] max(merged[-1][1], end)而不是merged[-1][1] end。一个区间可能被另一个完全包含例如 [1, 10] 和 [2, 5]若直接赋值end会把已合并区间的右端点错误地缩回去。这一取 max 而非覆盖的细节是合并类问题最普遍的 bug。进阶变形插入区间、无重叠区间可参见课后练习清单它们都是在排序 贪心扫描骨架上做文章。模式三动态规划——重叠子问题只算一次动态规划DP通过把问题分解为重叠的子问题overlapping subproblems每个子问题只求解一次并存储结果来避免重复计算。DP 适用需要两个条件最优子结构全局最优可由子问题最优构造和重叠子问题递归树中同一子问题反复出现。两种实现方式自顶向下记忆化 memoisation先写出自然的递归解法再把结果缓存到字典中自底向上表格法 tabulation从最小的子问题开始向上构建表格。如何识别 DP问题求最优值min/max、计数或存在性且当前决策依赖之前的决策。画出递归树如果看到重复子问题就是 DP。这里与第14章第00节的 Fibonacci 例子一脉相承朴素递归 $O(2^n)$记忆化后变为 $O(n)$。Easy爬楼梯问题$n$ 级台阶每次可以爬 1 或 2 级有多少种不同爬法这正是 Fibonacci$f(n) f(n-1) f(n-2)$。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$O(n)$ 时间、$O(1)$ 空间。因为每个状态只依赖前两个状态完整的记忆化表并不需要——这是 DP 空间优化滚动变量的最简单示范。Medium零钱兑换问题给定硬币面额和目标金额求凑出该金额所需的最少硬币数。状态dp[amount] 凑出amount所需的最少硬币数转移dp[amount] min(dp[amount - coin] 1)对每种硬币基础情形dp[0] 0。def coin_change(coins, amount): dp [float(inf)] * (amount 1) dp[0] 0 for a in range(1, amount 1): for coin in coins: if coin a and dp[a - coin] 1 dp[a]: dp[a] dp[a - coin] 1 return dp[amount] if dp[amount] ! float(inf) else -1关键陷阱用float(inf)而非 0 或 -1初始化。最小值比较只有在不可达状态是无穷大时才成立如果初始化成 0所有状态都会被错误地判定为用 0 个硬币凑出。这也是一个典型的无界背包unbounded knapsack——每种硬币可以用任意多次所以内层循环可以正序使用dp[a - coin]。当题目要求每种物品最多用一次时就退化为下面的 0/1 背包迭代方向必须反过来。Medium最长公共子序列问题给定两个字符串求它们最长公共子序列LCS的长度。状态dp[i][j]text1[:i]与text2[:j]的 LCS 长度转移若text1[i-1] text2[j-1]则dp[i][j] dp[i-1][j-1] 1否则dp[i][j] max(dp[i-1][j], dp[i][j-1])。def longest_common_subsequence(text1, text2): m, n len(text1), len(text2) dp [[0] * (n 1) for _ in range(m 1)] for i in range(1, m 1): for j in range(1, n 1): if text1[i - 1] text2[j - 1]: dp[i][j] dp[i - 1][j - 1] 1 else: dp[i][j] max(dp[i - 1][j], dp[i][j - 1]) return dp[m][n]这是二维 DP的代表两个索引分别对应两个字符串的前缀。dp表大小为 $(m1) \times (n1)$ 并让 0 行 0 列恒为 0从而自然处理空前缀边界因此访问字符串时要写成text1[i-1]这正是第14章第00节陷阱表中2D DP 的 off-by-onedp是 1 索引访问text1[i-1]所强调的点。编辑距离Edit Distance是同一结构换一个转移公式的经典变形。Hard0/1 背包问题给定若干物品的重量和价值以及容量 $W$在不超过 $W$ 的前提下最大化总价值。状态dp[i][w] 使用前 $i$ 个物品、容量为 $w$ 时的最大价值转移dp[i][w] max(dp[i-1][w], dp[i-1][w - weight[i]] value[i])跳过或取物品 $i$。def knapsack(weights, values, capacity): n len(weights) dp [[0] * (capacity 1) for _ in range(n 1)] for i in range(1, n 1): for w in range(capacity 1): dp[i][w] dp[i - 1][w] # skip item i if weights[i - 1] w: dp[i][w] max(dp[i][w], dp[i - 1][w - weights[i - 1]] values[i - 1]) return dp[n][capacity]空间优化每一行只依赖上一行因此可以用一维数组并让 $w$从右往左迭代def knapsack_optimised(weights, values, capacity): dp [0] * (capacity 1) for i in range(len(weights)): for w in range(capacity, weights[i] - 1, -1): # right to left! dp[w] max(dp[w], dp[w - weights[i]] values[i]) return dp[capacity]关键陷阱一维版本如果从左往右迭代dp[w - weights[i]]可能已经被本轮更新过等于允许物品 $i$ 被多次使用变成无界背包从右往左保证每个物品至多使用一次。方向这一字之差正是 0/1 背包与无界背包的分水岭。零钱兑换是无界背包、0/1 背包是每物一次两者在课后练习中经常结对出现务必分清。模式四回溯——带剪枝的穷举搜索回溯backtracking是带剪枝的穷举搜索增量式构建解一旦当前部分解不可能导向合法的完整解就立即放弃回溯。递归三要素是选择choose、探索explore、撤销选择unchoose。通用模板def backtrack(candidates, path, result): if is_solution(path): result.append(path[:]) # copy! return for candidate in get_candidates(path): if is_valid(candidate, path): path.append(candidate) # choose backtrack(candidates, path, result) # explore path.pop() # unchoose (backtrack)撤销选择是回溯区别于普通递归的关键没有path.pop()状态会不断累积后续候选看到的是被污染的旧状态。回溯的基础理论三步骤、剪枝的作用、子集/排列的递归树在第14章第00节有完整铺垫N-Queens 的剪枝收益$n^n$ → 约 $n!$也在那里给出了数量级对比。Medium子集def subsets(nums): result [] def backtrack(start, path): result.append(path[:]) for i in range(start, len(nums)): path.append(nums[i]) backtrack(i 1, path) path.pop() backtrack(0, []) return result每个部分解都是一个合法子集因此进入函数即记录。backtrack(i 1, ...)保证不重复使用元素、且按start索引避免回头从而生成全部 $2^n$ 个子集且无重复。Medium组合总和问题找出所有和为 target 的唯一组合元素可以重复使用。def combination_sum(candidates, target): result [] def backtrack(start, path, remaining): if remaining 0: result.append(path[:]) return for i in range(start, len(candidates)): if candidates[i] remaining: break # prune: sorted, so all further candidates are too large path.append(candidates[i]) backtrack(i, path, remaining - candidates[i]) # i, not i1: reuse allowed path.pop() candidates.sort() # sort for pruning backtrack(0, [], target) return result关键陷阱backtrack(i, ...)允许重复使用同一元素backtrack(i 1, ...)则跳到下一个元素不允许重复。搞混这两者是回溯题最常见的 bug——前者对应组合总和可重复取后者对应组合总和 II每个元素只能用一次。此外先排序再在candidates[i] remaining时break是剪枝的关键既然已排序后续候选只会更大整棵子树都可以跳过。HardN 皇后问题在 $n \times n$ 棋盘上放置 $n$ 个皇后使任意两个皇后互不攻击。def solve_n_queens(n): result [] cols set() pos_diag set() # (row col) is constant on / diagonals neg_diag set() # (row - col) is constant on \ diagonals board [[. ] * n for _ in range(n)] def backtrack(row): if row n: result.append([.join(r) for r in board]) return for col in range(n): if col in cols or (row col) in pos_diag or (row - col) in neg_diag: continue cols.add(col) pos_diag.add(row col) neg_diag.add(row - col) board[row][col] Q backtrack(row 1) cols.remove(col) pos_diag.remove(row col) neg_diag.remove(row - col) board[row][col] . backtrack(0) return result关键洞察对角线的编码方式。在/对角线上row col是常数在\对角线上row - col是常数。用三个集合列、两条对角线做冲突检测使合法性检查降为 $O(1)$配合逐行放置天然保证同行无冲突把 $O(n^n)$ 的暴力搜索剪枝到实用规模。常见陷阱速查表原文档将全文高频 bug 汇总为一张表是面试前的最后一页复习材料陷阱示例修复二分查找中lo hi与lo hi混用边界 off-by-one根据hi是闭区间还是开区间选择一维 0/1 背包从左到右迭代物品被多次使用0/1 背包必须从右往左迭代回溯中不复制 pathresult.append(path)——所有条目指向同一列表用result.append(path[:])或path.copy()backtrack(i)与backtrack(i1)混淆允许/禁止重复使用元素严格对照题目要求已排序回溯中缺少break继续探索过大的候选排序 候选超过剩余值时breakDP 初始化错误dp[0]错 → 后续全部错仔细定义并验证基础情形贪心未经证明贪心并不总是正确验证贪心选择性质多关键字排序用了不稳定排序相等元素的相对顺序丢失用稳定排序归并排序、Python 的sorted这八条几乎覆盖了排序搜索类题目 90% 的失分点。结合前文各小节可以总结出三条方法论二分先画两元素数组验证边界DP 先写状态定义、转移、基础情形再编码回溯先确认i还是i 1、path 是否深拷贝。课后练习清单按模式分组以下是原文档给出的配套练习路线按模式分组、由易到难每道题都在强化本文件的一个具体范式。建议每道题都先做模式识别这道题属于哪个范式、为什么再动手写代码。二分查找Binary Search——标准模板Search a 2D Matrix——展平矩阵上二分Koko Eating Bananas——二分答案Search in Rotated Sorted Array——识别有序半区Find Minimum in Rotated Sorted Array——二分找拐点Median of Two Sorted Arrays——基于分割点的二分贪心Jump Game——维护最远可达Jump Game II——BFS 式层级跟踪Merge Intervals——排序 合并Insert Interval——定位重叠区域Non-overlapping Intervals——按结束时间排序动态规划Climbing Stairs——Fibonacci DPHouse Robber——取/跳过 DPHouse Robber II——环形跑两次Coin Change——无界背包Longest Common Subsequence——双字符串二维 DPWord Break——集合查找 DPLongest Increasing Subsequence——$O(n^2)$ DP 或 $O(n \log n)$ 配合二分Edit Distance——经典二维 DPPartition Equal Subset Sum——0/1 背包变体回溯Subsets——枚举所有子集Combination Sum——带复用的回溯Permutations——used 集合回溯Subsets II——跳过重复Word Search——网格回溯Palindrome Partitioning——回溯 回文判断N-Queens——约束传播练习时注意本文件是第14章的收官之篇前面的数组与哈希双指针、滑动窗口、前缀和、链表/栈/队列、树、图构成了完整模式库排序搜索作为最后一个范式与其相互印证。如何在仓库中深度使用本文内容本仓库maths-cs-ai-compendium是 MkDocs 构建的开源教科书本章节在导航配置 mkdocs.yml 中注册为chapter 14: data structures and algorithms/05. sorting and search.md在 llms.txt 中登记的描述为Merge/quick sort, binary search, greedy, DP, backtracking (with NeetCode problems)与本文结构一一对应。仓库还附带一个MCP 服务器见 mcp/src/index.ts让 Claude Code、Cursor、VS Code 等 AI 助手把整本教科书当作知识库使用。其核心能力list_topics列出全部 20 章的章节结构输入chapter参数可过滤到指定章如第 14 章read_section按章号与节号读取完整内容例如chapter14, section5即可完整返回本节原文search跨全部章节做关键词检索返回命中上下文与行号recommend解析llms.txt的描述文本与停用词表基于学习目标推荐阅读顺序。使用 MCP 服务器需要先在本地克隆仓库然后配置 AI 客户端连接。阅读建议先完整精读本文件对应的原文档再对照 第14章第00节 Foundations 补足 Big O、递归、回溯、DP 的第一性原理最后用上面的练习清单检验模式识别能力——这套读原文 → 懂原理 → 练识别的路径正是本教科书先直觉后公式理念的落地方式。【免费下载链接】maths-cs-ai-compendiumBecome a cracked AI/ML researcher/engineer with this unconventional textbook covering maths, computing, and ML with intuition.项目地址: https://gitcode.com/GitHub_Trending/mat/maths-cs-ai-compendium创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价