news 2026/9/7 23:05:45

tech-interview-handbook 排序与搜索专题:复杂度对比、二分查找源码剖析与刷题路线

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
tech-interview-handbook 排序与搜索专题:复杂度对比、二分查找源码剖析与刷题路线

tech-interview-handbook 排序与搜索专题:复杂度对比、二分查找源码剖析与刷题路线

【免费下载链接】tech-interview-handbookCurated coding interview preparation materials for busy software engineers项目地址: https://gitcode.com/GitHub_Trending/te/tech-interview-handbook

本文基于 tech-interview-handbook 仓库中的排序与搜索专题速查文档(sorting-searching.md),完整覆盖其核心脉络:各排序算法的时间/空间复杂度对比、语言内置排序算法真相、二分查找及其变体的可运行源码实现、面试中必须识别的两类技巧(有序输入、有限值域),以及必备题与推荐刷题清单。读完后你应能:判断一道题该用二分还是直接调用语言默认排序;默写出无溢出风险的迭代版二分查找与 bisect 变体;并用仓库中自带的可运行参考实现自测。

排序与搜索为什么是同一个专题

排序(Sorting)是将序列中的元素按数值或字典序重新排列的操作,可以是升序也可以是降序。速查文档的开篇就给出了一个关键的面试认知:

一批基础排序算法的时间复杂度是 O(n²),不应该在面试中使用。在算法面试中,你几乎不需要从零实现任何排序算法。正确做法是用语言内置的排序函数对输入排序,使其可以被二分搜索。

也就是说,面试中排序的定位是"预处理手段"——先把输入排好,再叠加 O(log n) 的二分搜索。对于已排序数组,二分搜索利用其有序性质,将目标值与数组中间元素比较,从而确定目标位于左半还是右半,然后在剩下的半区中继续比较,直到找到目标或区间为空。

各排序算法复杂度总览

原文档给出了完整的复杂度对照表,这是面试中被问"说说常见排序的复杂度"时的标准答案,务必完整掌握:

算法时间复杂度空间复杂度
冒泡排序 Bubble sortO(n²)O(1)
插入排序 Insertion sortO(n²)O(1)
选择排序 Selection sortO(n²)O(1)
快速排序 QuicksortO(n log n)O(log n)
归并排序 MergesortO(n log n)O(n)
堆排序 HeapsortO(n log n)O(1)
计数排序 Counting sortO(n + k)O(k)
基数排序 Radix sortO(nk)O(n + k)
算法Big-O
二分搜索 Binary searchO(log n))

两个细节值得注意:Mergesort 的 O(n) 空间来自合并时需要额外数组(可参考后文 mergeSort.js 的实现);Heapsort 之所以能原地排序,是因为堆可以就地构建在数组上(参考 heap.py)。Counting sort 与 Radix sort 的复杂度依赖 k(值域大小/位数),它们是唯一的非比较排序,这也是后文"有限值域"技巧的理论基础。

面试必知:你语言的默认排序算法到底是什么

原文档特别提醒:必须知道你所用语言默认排序算法的时间和空间复杂度。其时间复杂度几乎肯定是 O(n log n);如果能说出具体算法名则是加分项。文档给出的事实如下:

  • Python 3.11+:默认排序算法是 Powersort,它取代了此前一直使用的 Timsort;
  • Java:对象排序使用 Timsort 的实现,基本类型(primitives)排序使用 Dual-Pivot Quicksort(双轴快排)。

这条信息在面试口头表达中很实用——比如被问"Python 的 sorted() 稳定吗?"时,可以顺着 Timsort/Powersort 的稳定性与 O(n log n) 复杂度展开。

边界情况(Corner Cases)

原文档列出的边界情况清单同样是二分与排序实现的自测清单,任何手写二分/排序代码都应逐条过一遍:

  • 空序列(Empty sequence)
  • 只有一个元素的序列
  • 有两个元素的序列
  • 含重复元素的序列

仓库中自带的参考实现正是按这套清单写的测试用例,例如 mergeSort.js 就依次验证了空数组、单元素、双元素、含重复元素([7, 2, 4, 3, 1, 2]期望[1, 2, 2, 3, 4, 7])、已有序数组与含负数的数组,可作为自测模板直接沿用。

二分搜索的无溢出迭代实现

速查文档的核心结论——"有序输入首先想到二分"——在仓库中有可直接运行的参考实现。JavaScript 版本见 binarySearch.js:

function binarySearch(arr, target) { let left = 0; let right = arr.length - 1; while (left <= right) { const mid = left + Math.floor((right - left) / 2); if (arr[mid] === target) { return mid; } if (arr[mid] < target) { left = mid + 1; } else { right = mid - 1; } } return -1; }

Python 等价实现见 binary_search.py:

def binary_search(arr, target): left = 0 right = len(arr) - 1 while left <= right: mid = left + (right - left) // 2 if arr[mid] == target: return mid elif arr[mid] < target: left = mid + 1 else: right = mid - 1 return -1

两处实现都体现了两个关键工程细节,面试手撕时应当刻意保留:

  1. 中点计算用left + (right - left) // 2而非(left + right) // 2。在 C/Java 等语言中left + right可能溢出,这种写法避免了该问题;
  2. 循环条件left <= right且返回 -1 表示未命中,而不是返回布尔值——返回下标能让调用方拿到目标位置。

两个文件末尾都附带了断言式自测:对[1, 2, 3, 10]分别查找首元素、中间元素、尾元素、不存在元素、小于首元素与大于尾元素的值,全部符合预期,覆盖了"命中/不命中/越界两侧"的组合。

bisect 变体:处理重复元素与"插入位置"

binary_search.py 还实现了二分搜索最重要的两个变体bisect_leftbisect_right,它们回答的问题不是"目标在哪",而是"目标应该插到哪里才能保持有序":

def bisect_left(arr, target): """Returns the leftmost position that `target` should go to such that the sequence remains sorted.""" left = 0 right = len(arr) while left < right: mid = (left + right) // 2 if arr[mid] < target: left = mid + 1 else: right = mid return left def bisect_right(arr, target): """Returns the rightmost position that `target` should go to such that the sequence remains sorted.""" left = 0 right = len(arr) while left < right: mid = (left + right) // 2 if arr[mid] > target: right = mid else: left = mid + 1 return left

注意与基础二分的三点结构差异:搜索区间右端是len(arr)而非len(arr) - 1(允许插入到末尾);循环条件是left < right(左闭右开区间);比较方向不同(bisect_left<bisect_right>)。文件内的自测用例(第 50-68 行)特别验证了重复元素场景:对[1, 2, 3, 3, 10]查找 3,bisect_left返回 2(第一个 3 的位置),bisect_right返回 4(最后一个 3 之后的位置)——这正是原文档"含重复元素的序列"这一边界情况的具体化。这类变体是 "Search in Rotated Sorted Array"、统计区间内元素个数等高频题的骨架。

归并排序实现:为什么它的空间是 O(n)

仓库中 mergeSort.js 提供了一个标准的递归归并排序实现,恰好印证了复杂度表中的空间一栏:

function mergeSort(arr) { if (arr.length < 2) { // Arrays of length 0 or 1 are sorted by definition. return arr; } const left = arr.slice(0, Math.floor(arr.length / 2)); const right = arr.slice(Math.floor(arr.length / 2), arr.length); return merge(mergeSort(left), mergeSort(right)); } function merge(arr1, arr2) { const merged = []; let i = 0, j = 0; while (i < arr1.length && j < arr2.length) { if (arr1[i] <= arr2[j]) { merged.push(arr1[i]); i++; } else if (arr2[j] < arr1[i]) { merged.push(arr2[j]); j++; } } merged.push(...arr1.slice(i), ...arr2.slice(j)); return merged; }

从源码结构看,每个slicemerged数组都会分配新内存,递归深度为 log n、每层合计复制 n 个元素,因此总空间开销是 O(n)——这就是它与原地排序(O(1) 空间的 Heapsort)的核心取舍,也是 Mergesort 稳定(<=比较保证相等元素保持原相对顺序)而 Heapsort 不稳定(见 heap.py 中_bubble_down选较小子节点时左子优先的写法)的原因。虽然面试不要求你默写归并排序,但理解"为什么需要额外 O(n) 空间"能在复杂度讨论中体现深度。文件末尾(第 34-50 行)的自测用例覆盖了原文档列出的全部边界情况:空数组、单元素、双元素、重复元素、逆序数组以及含负数数组。

从排序到选择:QuickSelect 与 K 大问题

原文档推荐练习题中包含 "Kth Largest Element in an Array" 这类 K 大问题。这类问题的最优解法不是完整排序,而是 QuickSelect——基于快排 partition 思想的线性时间选择算法。仓库中的 quick_select.py 给出了可运行实现:

def quick_select(array, k): """NOTE: k-th smallest element counts from 0!""" left = 0 right = len(array) while True: random_index = random.sample(range(left, right), 1)[0] array[left], array[random_index] = array[random_index], array[left] pivot_index = partition_first(array, left, right) if k == pivot_index: return array[pivot_index] if k < pivot_index: right = pivot_index else: left = pivot_index + 1

实现要点从源码中可以读出:随机化选轴元(避免最坏 O(n²));partition_first变体保证轴元落在其最终排序位置(这是正确性前提);每轮只递归/循环进入一侧,因此期望复杂度 O(n)。文件末尾(第 50-60 行)用 1000 个元素随机打乱 10 次的随机化测试来验证,这种"大规模随机自测"的写法在面试白板之外非常实用。

面试中的两类识别技巧

原文档的 Techniques 一节给出了两条题目识别规则,这是把"排序/搜索"专题落地到具体题目的桥梁:

1. 输入已经有序(Sorted inputs)

当给定序列本身有序(无论升序还是降序)时,二分搜索应该是你脑海中第一个跳出来的工具。

这一条覆盖的题型包括:有序数组/旋转有序数组中的搜索、二维有序矩阵的搜索、用二分"搜索答案"的单调性问题(如求最小的 x 使 f(x) 成立)。前面讲的 bisect 变体正是这条技巧的具体工具。

2. 值域有限的输入(Limited range)

计数排序(Counting sort)是一种非比较排序,适用于事先已知取值范围的数值。原文档给出的例子是 H-Index 问题。

当 n 很大但值域 k 很小(或 k 与 n 同量级)时,O(n + k) 的计数排序可以击败 O(n log n) 的比较排序,这也解释了复杂度表中为什么单独列出 Counting sort 与 Radix sort。

刷题清单:必备题与推荐题

原文档将练习分为"必备题"(学习本专题时优先刷)与"推荐题"(学完必备题后刷),以下为完整继承的清单:

必备题(Essential questions):

  • Binary Search(LeetCode 704)——二分基础模板题
  • Search in Rotated Sorted Array(LeetCode 33)——有序性被破坏后的二分

推荐练习题(Recommended practice questions):

  • Kth Smallest Element in a Sorted Matrix(LeetCode 378)——有序结构上的二分
  • Search a 2D Matrix(LeetCode 74)——把二维矩阵"摊平"成一维二分
  • Kth Largest Element in an Array(LeetCode 215)——QuickSelect 的直接应用
  • Find Minimum in Rotated Sorted Array(LeetCode 153)——旋转数组中用二分找拐点
  • Median of Two Sorted Arrays(LeetCode 4)——两个有序数组上的二分,难度上限

这份清单与仓库内参考实现的对应关系很直接:旋转数组两题练"有序性判定",Kth 两题练"二分/选择代替全排序",矩阵两题练"降维后二分",Median 一题练"对答案空间二分"。

学习资源(按原文档继承)

原文档按"阅读/加餐/视频"三层组织了学习资源。外部链接因平台规范不再列出,但保留其来源与主题供检索:

  • 核心阅读:basecs 的排序算法基础文、Khan Academy 的 Binary Search 讲解;
  • 加餐(有时间再看):basecs 系列文章,覆盖 Selection Sort、Bubble Sort、Insertion Sort、Merge Sort(上下篇)、Quicksort(上下篇)、Counting Sort、Radix Sort;
  • 视频系列:剑桥大学 Samuel Albanie 的算法短视频,覆盖 Heapsort、Quicksort、比较排序下界(Lower bounds for comparison sorts)、Counting sort、Radix sort、Bucket sort,每支视频均配有 slides。

仓库本身也提供了算法课程的推荐入口(见 AlgorithmCourses.md),按专题文档的引用方式挂载在本页末尾。

小结

排序与搜索专题在面试中的真实分工是:排序靠语言内置函数(Python 3.11+ 的 Powersort、Java 的 Timsort/双轴快排),搜索才是手撕重点。建议的掌握路径是:先背下复杂度对照表 → 用 binarySearch.js 与 binary_search.py 把基础二分和 bisect 变体各手撕两遍并跑通自带自测用例 → 理解 mergeSort.js 的 O(n) 空间来源 → 通过 quick_select.py 建立"选择代替排序"的直觉 → 按必备题、推荐题顺序完成刷题,并对每个实现逐条核对原文档的四类边界情况(空、单元素、双元素、重复元素)。

【免费下载链接】tech-interview-handbookCurated coding interview preparation materials for busy software engineers项目地址: https://gitcode.com/GitHub_Trending/te/tech-interview-handbook

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 23:03:16

移动端适配基石:彻底搞懂 Viewport 与视口单位

做移动端页面调试时&#xff0c;你一定遇到过这样的场景&#xff1a;PC 上用 DevTools 模拟手机一切正常&#xff0c;真机一打开&#xff0c;字小到要双指放大才能看清&#xff0c;页面横向还多出一截&#xff0c;底部按钮被地址栏遮得严严实实。为了这些移动浏览问题&#xff…

作者头像 李华
网站建设 2026/9/7 23:02:39

AI Agent安全沙箱:容器与微虚拟机隔离技术解析

1. AI Agent代码执行沙箱的核心挑战 在AI Agent开发领域&#xff0c;代码执行沙箱是确保系统安全的关键组件。我经历过多次由于沙箱隔离不足导致的安全事故&#xff0c;最严重的一次是恶意代码通过AI Agent逃逸到宿主系统&#xff0c;删除了整个数据库。这种惨痛教训让我深刻认…

作者头像 李华
网站建设 2026/9/7 23:02:15

COMSOL多物理场仿真从建模到交付:耦合、网格与不收敛排查全攻略

1. 多物理场仿真为什么成了工程师的硬需求&#xff0c;代做市场又从哪来接触COMSOL仿真代做这个圈子&#xff0c;已经有年头了。最开始只是因为自己在课题组里用COMSOL做项目&#xff0c;后来陆陆续续有人找过来问"能不能帮我算个东西"&#xff0c;再后来干脆形成了一…

作者头像 李华
网站建设 2026/9/7 23:01:26

入境消费涨近三成,小城游客翻六倍

《入境消费已从观光走向带货》——游客带走的不只是商品&#xff0c;更是对中国的新认知“过去&#xff0c;外国游客来中国看风景&#xff1b;如今&#xff0c;他们顺手把中国生活方式装进行李箱。”今年1—7月&#xff0c;境外人员在华消费2636亿元&#xff0c;同比增长27.8%&…

作者头像 李华
网站建设 2026/9/7 23:01:01

xxl-job分布式任务调度平台搭建与SpringBoot集成实战

做后端开发的朋友&#xff0c;几乎都会遇到定时任务的场景。用户签到提醒、对账跑批、数据同步、订单超时关闭……这些业务里都藏着定时任务。很多人一开始图省事&#xff0c;直接在SpringBoot里用Scheduled&#xff0c;本地跑得好好的&#xff0c;到了线上两台实例一部署&…

作者头像 李华
网站建设 2026/9/7 22:58:38

Python操作MySQL游标全解:从PyMySQL基础到存储过程与性能优化

先聊一个特别基础、但很多人一直没搞透的东西&#xff1a;Python操作MySQL时&#xff0c;那个谁都会用、却很少正经分析过的“游标”&#xff08;cursor&#xff09;。我第一次用Python连MySQL写业务代码时&#xff0c;根本不知道自己已经在一个游标上操作了。那时候我只知道要…

作者头像 李华