Skip to content

数据结构高频题

数据结构面试一般问两层:一是常见容器的底层实现与复杂度,二是选型依据。背完复杂度表后一定要能说出"什么场景选什么"。

Q1: 数组和链表的区别?各自适用场景? 「🟢 校招/初级」

考察点:最基础的存储结构认知。

参考答案

维度数组链表
内存连续分配,缓存友好节点分散,靠指针串联
随机访问O(1)O(n)
头部/中间插入删除O(n)(需搬移)O(1)(已知节点时)
空间需预分配,可能浪费按需分配,但有指针开销
  • 选型:读多写少、按下标访问选数组;频繁在头尾增删选链表(或双端队列)。

追问延伸

  • 为什么数组"缓存友好"?(CPU 缓存行的空间局部性)
  • Java ArrayList 和 LinkedList 的实际性能差异为什么和理论不一致?

Q2: 栈和队列的区别?有哪些实际应用? 「🟢 校招/初级」

考察点:线性结构的语义差异及应用意识。

参考答案

  • :后进先出(LIFO)。应用:函数调用栈、括号匹配、表达式求值、浏览器后退、撤销操作。
  • 队列:先进先出(FIFO)。应用:任务调度、消息队列、BFS、缓冲区。
  • 常见变体:双端队列(Deque)、优先队列(堆实现)、循环队列(避免假溢出)。

追问延伸

  • 用两个栈怎么实现队列?
  • 循环队列怎么判断满和空?

Q3: HashMap 的底层实现原理? 「🟡 中级」

考察点:最高频数据结构题,能决定面试走向。

参考答案(以 Java 8 为例):

  • 结构:数组 + 链表 + 红黑树。hash(key) & (n-1) 定位桶,冲突用拉链法。
  • 链表长度超过 8 且数组长度 ≥ 64 时转红黑树(O(n)→O(log n)),长度低于 6 退化回链表。
  • 负载因子 0.75,扩容为 2 倍;容量保持 2 的幂是为了让取模变成位运算。
  • Java 8 头插法改尾插法,解决了多线程扩容时的死循环问题,但 HashMap 本身仍非线程安全。

追问延伸

  • 为什么容量必须是 2 的幂?
  • 为什么不用红黑树而用链表做第一层?(空间与常数开销)
  • 线程安全场景选 ConcurrentHashMap 还是 Collections.synchronizedMap?

Q4: 说一下堆的实现和应用 「🟡 中级」

考察点:优先队列的理解。

参考答案

  • 堆是完全二叉树,用数组存储;父节点 i 的左右孩子在 2i+12i+2
  • 大顶堆/小顶堆:插入时上浮(sift up),删除堆顶时下沉(sift down),都是 O(log n);建堆 O(n)。
  • 应用:优先队列、TopK 问题(维护大小为 K 的小顶堆)、堆排序、定时器调度(时间轮之外的简单实现)。

追问延伸

  • 从 10 亿个数里找最大的 100 个,怎么做?
  • 为什么建堆是 O(n) 而不是 O(n log n)?

Q5: 跳表是什么?为什么 Redis 用跳表不用红黑树? 「🟡 中级」

考察点:对工程选型(而非纯理论)的理解。

参考答案

  • 跳表在有序链表上建立多层索引,每层以约 1/2 概率晋升,查找/插入/删除平均 O(log n)。
  • Redis ZSet 选跳表的原因:实现简单;范围查询(ZRANGE)天然友好;通过调整晋升概率可灵活平衡内存和速度;并发友好(局部修改)。
  • 红黑树实现复杂、范围查询需要中序遍历、旋转涉及多个节点,不利于并发修改。

追问延伸

  • 跳表的空间复杂度是多少?
  • LevelDB/RocksDB 的 memtable 也用了跳表,为什么?

Q6: 布隆过滤器了解吗? 「🟡 中级」

考察点:解决缓存穿透等实际问题的数据结构储备。

参考答案

  • 用 m 位比特数组 + k 个哈希函数判断元素"可能存在":写入时把 k 个位置置 1,查询时任一位置为 0 则一定不存在。
  • 特点:有假阳性(说"存在"可能不存在),无假阴性;不支持删除(计数布隆过滤器可以)。
  • 应用:缓存穿透防护、爬虫 URL 去重、垃圾邮件过滤、HBase 加速读。

追问延伸

  • 误判率和哪些参数相关?怎么估算?
  • 缓存穿透还有什么解法?(空值缓存、互斥锁,见 Redis 篇)