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 并发。协程是用户态的轻量级并发单元,切换开销小,通常通过 asyncioasync/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面试实战专栏 文章被收录于专栏

本专栏聚焦 AI-Agent 面试高频考点,内容来自真实面试与项目实践。系统覆盖大模型基础、Prompt工程、RAG、Agent架构、工具调用、多Agent协作、记忆机制、评测、安全与部署优化等核心模块。以“原理+场景+实战”为主线,提供高频题解析、标准答题思路与工程落地方法,帮助你高效查漏补缺.

全部评论

相关推荐

评论
点赞
2
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务