🎁 立即注册,最多可免费使用 30 分钟在线 AI,无需信用卡。

软件工程师面试题及答案(2026):编码、基础知识、系统设计与行为面试

October 14, 2026
20 道软件工程师面试题及答案:8 道附经过测试的 Python 编码题解,以及计算机基础、系统设计、行为面试题和备考方法。
软件工程师面试题及答案(2026):编码、基础知识、系统设计与行为面试
软件工程师面试题
软件工程面试
编程面试题
软件工程师面试准备
软件开发工程师面试题

在大多数公司,软件工程师面试都遵循一个熟悉的流程:招聘人员电话、一到两轮编码、面向中级和高级岗位的系统设计轮,以及行为面试轮。下面按轮次整理了 20 道软件工程师面试题及答案。发布前,每段代码示例都已用成千上万组随机输入与暴力解法进行对比测试。

每一轮考察什么

轮次常见形式考察重点
招聘人员初筛电话沟通 20 到 30 分钟背景、求职动机、薪资范围、流程安排
编码在共享编辑器中进行 45 到 60 分钟解题能力、代码整洁度、测试意识、边写边讲
计算机基础穿插在编码轮中,或作为单独一轮你是否理解自己每天使用的系统
系统设计在白板或文档上进行 45 到 60 分钟权衡取舍、规模化能力、清晰表达
行为面试30 到 45 分钟主人翁意识、协作能力、如何应对失败

应届生面试以编码和行为面试为主。资深岗位会增加系统设计,并期待你对过往项目给出更深入的回答。关于招聘人员电话,请参阅我们的电话初筛面试题。

编码题

每一轮编码面试中,动手打字前先说明思路,最后给出时间和空间复杂度,并用一个小例子进行测试。复杂度部分可以参考我们的大 O 速查表。

1. 两数之和:返回相加等于目标值的两个数的下标

把每个数字及其下标存入哈希表。每读到一个新数字,就检查它的补数是否已经在表中。

def two_sum(nums, target):
    seen = {}
    for i, x in enumerate(nums):
        if target - x in seen:
            return [seen[target - x], i]
        seen[x] = i
    return []

print(two_sum([2, 7, 11, 15], 9))  # [0, 1]

时间复杂度 O(n),空间复杂度 O(n)。暴力解法要检查每一对数字,时间复杂度为 O(n²)。面试官通常希望你先提到这个解法,再进行优化。

2. 有效括号:检查每个括号是否按正确的顺序闭合

把左括号压入栈中。遇到右括号时,栈顶必须是与之匹配的左括号。

def is_valid(s):
    pairs = {")": "(", "]": "[", "}": "{"}
    stack = []
    for ch in s:
        if ch in pairs:
            if not stack or stack.pop() != pairs[ch]:
                return False
        else:
            stack.append(ch)
    return not stack

print(is_valid("([]{})"), is_valid("(]"))  # True False

时间复杂度 O(n),空间复杂度 O(n)。最后一行很关键:像 “((” 这样的字符串会在栈中留下元素,因此它不是有效的括号。

3. 合并重叠区间

先按起始时间排序,然后要么延长最后一个已合并的区间,要么开始一个新区间。

def merge_intervals(intervals):
    merged = []
    for start, end in sorted(intervals):
        if merged and start <= merged[-1][1]:
            merged[-1][1] = max(merged[-1][1], end)
        else:
            merged.append([start, end])
    return merged

print(merge_intervals([[1, 3], [2, 6], [8, 10], [15, 18]]))
# [[1, 6], [8, 10], [15, 18]]

由于排序,时间复杂度为 O(n log n)。可以先问面试官,像 [1, 2] 和 [2, 3] 这种首尾相接的区间是否应该合并。本版本会将它们合并。

4. 反转链表

遍历一次链表,让每个节点指向它的前一个节点。

class ListNode:
    def __init__(self, val, next=None):
        self.val = val
        self.next = next

def reverse_list(head):
    prev = None
    while head:
        head.next, prev, head = prev, head, head.next
    return prev

时间复杂度 O(n),额外空间复杂度 O(1)。如果面试官觉得这种多重赋值写法太取巧,可以改写成三行,借助一个临时变量。清晰比简短更重要。

5. 返回出现频率最高的 k 个元素

先用哈希表统计次数,再用堆取出次数最大的 k 个。

from collections import Counter
import heapq

def top_k_frequent(nums, k):
    counts = Counter(nums)
    return [x for x, _ in heapq.nlargest(k, counts.items(), key=lambda p: p[1])]

print(top_k_frequent([1, 1, 1, 2, 2, 3], 2))  # [1, 2]

时间复杂度 O(n log k)。面试官可能追问:按频率进行桶排序可以把复杂度降到 O(n)。

6. 设计一个 get 和 put 都是 O(1) 的 LRU 缓存

你既需要快速查找,又需要按最近使用顺序快速调整位置。哈希表加双向链表可以同时满足这两点。在 Python 中,OrderedDict 正好就是这种组合。

from collections import OrderedDict

class LRUCache:
    def __init__(self, capacity):
        self.capacity = capacity
        self.data = OrderedDict()

    def get(self, key):
        if key not in self.data:
            return -1
        self.data.move_to_end(key)
        return self.data[key]

    def put(self, key, value):
        if key in self.data:
            self.data.move_to_end(key)
        self.data[key] = value
        if len(self.data) > self.capacity:
            self.data.popitem(last=False)

有些面试官会要求你自己实现链表。先说明结构:哈希表指向链表节点,最近使用的节点位于一端,淘汰时从另一端移除。

7. 带障碍物网格中的最短路径

当每一步的代价都相同时,广度优先搜索可以找到最短路径。

from collections import deque

def shortest_path(grid):
    rows, cols = len(grid), len(grid[0])
    if grid[0][0] or grid[rows - 1][cols - 1]:
        return -1
    queue = deque([(0, 0, 0)])
    seen = {(0, 0)}
    while queue:
        r, c, steps = queue.popleft()
        if (r, c) == (rows - 1, cols - 1):
            return steps
        for dr, dc in ((1, 0), (-1, 0), (0, 1), (0, -1)):
            nr, nc = r + dr, c + dc
            if 0 <= nr < rows and 0 <= nc < cols and not grid[nr][nc] and (nr, nc) not in seen:
                seen.add((nr, nc))
                queue.append((nr, nc, steps + 1))
    return -1

print(shortest_path([[0, 0, 0], [1, 1, 0], [0, 0, 0]]))  # 4

时间和空间复杂度均为 O(rows × cols)。把单元格加入队列时就标记为已访问,而不是出队时才标记,否则同一个单元格可能会被多次入队。

8. 找出第一个坏版本

版本 1 到 n 在某个点之前都是好的,之后都是坏的。二分查找可以用 O(log n) 次检查找到第一个坏版本。

def first_bad_version(n, is_bad):
    lo, hi = 1, n
    while lo < hi:
        mid = (lo + hi) // 2
        if is_bad(mid):
            hi = mid
        else:
            lo = mid + 1
    return lo

这个模式适用于任何只会变化一次的是非判断问题,这类题目很常见。如果想找覆盖这些模式的题目清单,请参阅 Blind 75。

计算机基础题

9. 进程和线程有什么区别?

进程是一个正在运行的程序,拥有自己独立的内存空间。线程运行在进程内部,共享该进程的内存。线程的创建和切换开销更小,并且可以直接共享数据,但正是这种共享使得我们需要锁,也会出现竞态条件。进程之间相互隔离,因此一个进程崩溃不会拖垮其他进程。

10. TCP 和 UDP 有什么区别?

TCP 会先建立连接,并通过重传和流量控制保证数据有序、可靠地送达。UDP 发送的是彼此独立的数据包,不保证送达和顺序,但开销更小。网页、API 和文件传输适合使用 TCP。实时语音、视频和游戏更适合 UDP,因为迟到的数据包本来就没有用。想深入了解,可以看我们的计算机网络面试题。

11. 在浏览器中输入网址并按下回车后会发生什么?

浏览器先通过 DNS 把域名解析为 IP 地址,然后建立 TCP 连接,对于 HTTPS 还要进行 TLS 握手。接着发送 HTTP 请求,服务器(通常位于负载均衡器之后)返回响应。浏览器解析 HTML,请求其中引用的 CSS、JavaScript 和图片,然后渲染页面。面试官选哪一部分,你就深入讲哪一部分。

12. 哈希表是如何工作的?它的时间复杂度是多少?

哈希函数把每个键映射到数组中的一个桶。平均情况下,查找、插入和删除的时间复杂度都是 O(1)。当两个键落入同一个桶时,哈希表通过链表法或探测其他槽位来处理冲突,并在太满时扩容。如果大量键发生冲突,最坏情况为 O(n)。

13. 什么时候应该选择 SQL 数据库而不是 NoSQL 数据库?

当数据具有关系性、需要连接查询和事务,并且结构相对稳定时,选择 SQL,例如订单、支付和用户账户。当访问模式简单、数据结构多变,或者需要把写入扩展到多台机器上时,选择文档或键值存储。很多系统会同时使用两者。面试官想听的是权衡取舍,而不是一个非此即彼的结论。通常位于其上层的 API 部分,可以参阅我们的REST API 面试题。

系统设计题

14. 设计一个短链接服务

先明确需求:每天新增多少链接、读取量有多大、链接是否会过期、是否允许自定义别名。然后讲清楚 API(创建和跳转)、短码的生成方式(用 62 进制编码的计数器,或带唯一性检查的随机码)、存储(从短码到原始网址的键值存储),以及针对热门链接的缓存,因为读请求远多于写请求。如果时间允许,最后再讲数据分析和滥用防护。我们在系统设计指南中完整讲解了这个设计。

15. 设计一个限流器

先确定限流在哪里执行(API 网关层,按用户或按 API 密钥),以及客户端超出限制后会发生什么(返回 HTTP 429 并附带重试时间)。常见的选择是令牌桶:每个客户端有一个桶,按固定速率补充令牌,桶中最多存放一定数量的令牌,每个请求消耗一个令牌。它允许短时间的突发流量,同时保证平均速率不超过限制。如果有多台服务器,可以把计数器放在 Redis 等共享存储中,让每台服务器看到的计数保持一致。想要更多练习,请参阅我们的系统设计面试题。

行为面试题

每个故事都使用清晰的结构:背景、你做了什么、结果如何。有数据的话尽量写出具体数字。

16. 请介绍一下你自己

用两分钟讲述你的职业经历,最后说明你为什么想要这个岗位。提及一两个有具体成果的项目。我们关于如何介绍自己的指南里有示例。

17. 讲一个你修复过的棘手 bug

示例回答:“我们的结账服务每天会出现几次超时,而且没有规律。我在日志中加入了请求 ID,发现每次超时都来自同一条数据库查询,而这条查询只在购物车超过 50 件商品时才会执行。查询的连接字段缺少索引。我加上索引后,慢查询的耗时从大约 4 秒降到 50 毫秒以下,并且为该查询的延迟加了告警,以便下次能更早发现问题。”

18. 讲一次你和同事意见不一致的经历

示例回答:“一位同事想用新框架重写我们的通知服务,而我认为上线前风险太高。我没有在会上争执,而是建议我们各自把成本和风险写下来。最后我们达成一致:上线前先修复当前代码中最严重的两个问题,把重写安排在下个季度。版本按时上线了,两个月后我们带着完整的测试方案完成了重写。”

19. 讲一个失败的项目

选一个你负责过其中一部分的真实失败案例。说明哪里出了问题,如果重来会怎么做,之后又做了哪些改变。面试官看重的是诚实和学习能力,而不是完美的履历。

20. 你为什么想来我们这里工作?

把公司产品或工程工作中的一个具体点,与你自己的经历联系起来。“我用你们的 API 已经两年了,希望能参与平台方面的工作”,比“你们是一家很棒的公司”要好得多。

可以问面试官的问题

  • 团队里的人一般一周是怎么过的?
  • 工作是如何规划的?优先级由谁决定?
  • 这里的代码审查和部署是怎么运作的?
  • 如果入职后的前六个月做得好,会是什么样子?
  • 团队目前在攻克的最难的技术问题是什么?

更多内容请参阅面试结束时可以问的问题。

如何备考

  1. 选定一门语言,所有练习题都用它来做。Python 最常见,因为代码简短。关于语言本身的内容,可以参阅我们的Python 面试题。
  2. 按题型练习,而不是比谁做得多:哈希表、双指针、滑动窗口、二分查找、BFS 和 DFS、堆,以及动态规划。
  3. 计时并出声做题。无声做出正确答案,得分反而低于清楚地讲出来的答案。
  4. 用你面试时会用的编辑器练习。很多公司使用 CoderPad,它提供免费的沙盒环境。
  5. 准备五个来自工作经历的故事,让它们能灵活套用到大多数行为面试题上。
  6. 如果应聘中级或高级岗位,至少做几次完整的系统设计练习,并请人对你进行追问。

出声练习编程面试

YesToTheOffer 会转录你的回答,方便你回看自己是如何讲解解法的。截图编程辅助支持 0.2 倍速。30 分钟免费,无订阅。

iOS & Android — 即将推出
查看价格