Shopee AI Agent开发 一面
1、自我介绍
2、项目介绍
3、MySQL 和 PostgreSQL 的区别,分别用了什么数据结构实现
MySQL 和 PostgreSQL 都是关系型数据库,但工程定位不太一样。MySQL 在互联网业务里用得更多,生态成熟,部署方便,读写性能稳定,适合常规 OLTP 场景。PostgreSQL 功能更完整,SQL 标准支持更强,扩展能力更好,像 JSONB、数组、GIS、自定义类型、全文检索、窗口函数这些都更强,复杂查询能力通常更好。
从数据结构上看,MySQL 的 InnoDB 存储引擎主键索引采用的是 B+ 树,主键索引是聚簇索引,数据和主键放在一起;二级索引叶子节点存的是主键值,所以二级索引查找之后经常还需要回表。PostgreSQL 默认常用的是 B-Tree 索引,底层同样是平衡树结构,同时也支持 Hash、GIN、GiST、BRIN 等多种索引,适合不同类型的数据和查询场景。
4、B 树和 B+ 树的区别
B 树和 B+ 树都是多路平衡查找树,目标都是减少磁盘 IO。
B 树的特点是每个节点都可以存 key 和 data,非叶子节点有可能直接命中数据。B+ 树通常只有叶子节点存完整数据,非叶子节点只存索引 key,用来导航查找。B+ 树的叶子节点一般还会串成有序链表,因此范围查询和顺序扫描会更高效。
数据库里更常用 B+ 树,主要有几个原因:
- 非叶子节点不存实际数据,单个节点能放更多 key,树更矮
- 所有数据都在叶子节点,查询路径更稳定
- 叶子节点链表天然适合范围查询、排序和扫描
5、Redis 的主要功能,为什么要用 Redis 实现消息队列
Redis 是一个基于内存的高性能键值数据库,常见用途包括缓存、计数器、排行榜、分布式锁、会话管理、发布订阅和消息队列。它的核心优势就是快,读写延迟低。
Redis 实现消息队列的方式有多种。简单场景可以用 List,比如 LPUSH + BRPOP;更完整的消息队列能力一般用 Stream,因为它支持:
- 持久化
- 消费组
- ack 机制
- 消息回溯
- 未确认消息管理
相比 Python 内置队列,Redis 更适合分布式系统。Python 的 queue.Queue 更适合同进程线程间通信,multiprocessing.Queue 更适合同机多进程通信,但它们都不适合多机器、多服务共享消息。Redis 作为独立服务,可以跨进程、跨机器使用,而且可扩展性和可观测性更强。
6、Python 的 GIL 是什么,进程、线程、协程分别是什么
GIL 是 Global Interpreter Lock,也就是全局解释器锁。CPython 解释器里,同一时刻只有一个线程能执行 Python 字节码,所以多线程在 CPU 密集型任务上不能真正利用多核,但在 IO 密集型场景里仍然有价值,因为线程等待 IO 时会释放执行机会。
进程是资源分配的基本单位,每个进程有独立的地址空间和系统资源,隔离性强,适合 CPU 密集型任务。线程是调度的基本单位,同一进程里的线程共享内存,适合 IO 并发。协程是用户态的轻量级并发单元,切换开销小,通常通过 asyncio 和 async/await 实现,非常适合高并发网络请求、爬虫、异步服务这类 IO 密集场景。
简单说:
- CPU 密集型一般优先多进程
- IO 密集型可以用多线程或协程
- 协程更轻量,适合高并发 IO
7、怎么用两个栈实现队列
队列是先进先出,栈是先进后出。用两个栈实现队列的关键在于顺序翻转。
一个栈 in_stack 负责入队,一个栈 out_stack 负责出队。入队时直接往 in_stack 压入元素。出队时如果 out_stack 不为空,就直接弹出;如果为空,就把 in_stack 里的元素逐个弹出压到 out_stack,这样顺序就被翻转了,最先进入队列的元素会在 out_stack 顶部。
class MyQueue:
def __init__(self):
self.in_stack = []
self.out_stack = []
def push(self, x):
self.in_stack.append(x)
def pop(self):
self.peek()
return self.out_stack.pop()
def peek(self):
if not self.out_stack:
while self.in_stack:
self.out_stack.append(self.in_stack.pop())
return self.out_stack[-1]
def empty(self):
return not self.in_stack and not self.out_stack
均摊时间复杂度上,入队和出队都是[O(1)]
8、怎么判断链表相交
判断两个单链表是否相交,经典方法是双指针。两个指针分别从两条链表的头节点开始走,走到末尾后切换到另一条链表的头节点继续走。这样两个指针最终都会走过:
[len(A) + len(B)]
如果两个链表相交,它们会在交点相遇;如果不相交,最后都会走到 None。
class ListNode:
def __init__(self, x):
self.val = x
self.next = None
def getIntersectionNode(headA, headB):
p1, p2 = headA, headB
while p1 != p2:
p1 = p1.next if p1 else headB
p2 = p2.next if p2 else headA
return p1
9、冒泡排序的时间复杂度
冒泡排序每一轮通过相邻元素比较,把当前未排序部分中的最大值“冒”到最后面。它的平均时间复杂度和最坏时间复杂度都是:
O(n2)
如果数组本身已经有序,并且实现中加了“本轮是否发生交换”的优化,那么最好时间复杂度可以达到:
O(n)
空间复杂度是:
O(1)
因为它是原地排序。
10、HTTP 里面 GET 和 POST 的区别,请求头有哪些常见字段
HTTP 请求通常由请求行、请求头、空行和请求体组成。GET 和 POST 的区别主要在语义和参数位置。
GET 一般用于获取资源,参数通常放在 URL 查询串里,强调幂等和可缓存;POST 一般用于提交数据或者创建资源,参数通常放在请求体里,不强调幂等。GET 和 POST 都有请求头,不是说只有 POST 才有请求头。
常见请求头包括:
Host:目标主机User-Agent:客户端信息Accept:客户端可接受的数据类型Content-Type:请求体数据类型A
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
本专栏聚焦 AI-Agent 面试高频考点,内容来自真实面试与项目实践。系统覆盖大模型基础、Prompt工程、RAG、Agent架构、工具调用、多Agent协作、记忆机制、评测、安全与部署优化等核心模块。以“原理+场景+实战”为主线,提供高频题解析、标准答题思路与工程落地方法,帮助你高效查漏补缺.