第一部分:数据结构与算法
这是面试的重中之重,通常会通过1-2道算法题来考察。 1:二叉树的最近公共祖先 描述:** 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]
_______3______
/ \
___5__ ___1__
/ \ / \
6 _2_ 0 8
/ \
7 4
节点5和节点1的最近公共祖先是节点3。
节点5和节点4的最近公共祖先是节点5。
考察点:
- 树的遍历(前序、中序、后序)
- 递归思想
- 对树结构的理解
解题思路: 这是一个非常经典的树问题,最优雅的解法是使用递归,核心思想是:从根节点开始,递归地在左右子树中寻找p和q节点。

-
基本情况:
- 如果当前节点为空,说明没找到,返回
null。 - 如果当前节点等于p或q,说明找到了其中一个节点,那么这个节点本身就可能是祖先(如果另一个节点在它的子树里),所以返回当前节点。
- 如果当前节点为空,说明没找到,返回
-
递归情况:
- 分别递归左子树和右子树,得到
left和right。 - 判断
left和right的结果:left和right都不为null,说明p和q分别位于当前节点的左右两侧,那么当前节点就是它们的最近公共祖先,返回当前节点。left为null,说明p和q都在右子树,那么右子树递归的结果就是最近公共祖先,返回right。right为null,说明p和q都在左子树,那么左子树递归的结果就是最近公共祖先,返回left。
- 分别递归左子树和右子树,得到
代码示例 (Java):
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
// 基本情况1: 如果树为空,或者当前节点是p或q之一
if (root == null || root == p || root == q) {
return root;
}
// 递归地在左右子树中查找
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
// 判断结果
if (left != null && right != null) {
// p和q分别在左右子树,当前节点就是LCA
return root;
}
// 如果left为空,说明p和q都在右子树
// 如果right为空,说明p和q都在左子树
// 如果left和right都为空,说明p和q都不在当前子树
return left != null ? left : right;
}
}
2:反转链表 描述:** 反转一个单链表。

示例:
输入: 1 -> 2 -> 3 -> 4 -> 5 -> NULL
输出: 5 -> 4 -> 3 -> 2 -> 1 -> NULL
考察点:
- 链表的基本操作
- 指针的运用
- 迭代与递归的实现
解题思路: 这是链表操作的入门题,主要考察对指针的掌控能力,最常用的是迭代法。
迭代法思路:
- 定义三个指针:
prev(前驱节点,初始化为null),curr(当前节点,初始化为head),next(后继节点)。 - 遍历链表,在每一步:
- 先用
next暂存curr的下一个节点,因为马上要断开这个链接。 - 将
curr.next指向prev,实现反转。 - 将
prev和curr指针向后移动,准备处理下一个节点。
- 先用
- 当
curr变为null时,prev就指向了新的头节点。
代码示例 (Java):
class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
}
class Solution {
public ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode curr = head;
while (curr != null) {
ListNode nextTemp = curr.next; // 暂存下一个节点
curr.next = prev; // 反转指针
prev = curr; // prev后移
curr = nextTemp; // curr后移
}
return prev; // 当curr为null时,prev是新的头节点
}
}
递归法思路(拓展):
递归的思路稍微抽象一些,假设 reverseList(head) 函数已经能反转以 head 为头节点的链表并返回新的头节点。
- 递归终止条件:
head为空或head.next为空(即只有一个节点)。 - 递归调用:
newHead = reverseList(head.next),这会反转从head.next开始的链表,并返回新的头节点(即原链表的尾节点)。 - 操作:
head.next.next = head;将当前节点head添加到反转后链表的尾部。 head.next = null;断开原来的链接,防止形成环。- 返回
newHead。
第二部分:数据库
3:SQL查询
描述:**
假设有一个用户表 users 和一个订单表 orders。
users 表结构:
| user_id (主键) | username |
| :--- | :--- |
| 1 | 'Alice' |
| 2 | 'Bob' |
| 3 | 'Charlie' |
orders 表结构:
| order_id (主键) | user_id (外键) | amount |
| :--- | :--- | :--- |
| 101 | 1 | 100 |
| 102 | 2 | 150 |
| 103 | 1 | 200 |
| 104 | 4 | 300 |
问题:
请编写一条SQL语句,查询所有下过订单的用户信息(包括没有下过订单的用户,用户名显示为 "Inactive"),并按照 user_id 升序排列。
考察点:
LEFT JOIN(或LEFT OUTER JOIN) 的使用CASE WHEN条件表达式COALESCE或IFNULL函数(作为备选方案)ORDER BY排序
解题思路:
这个问题需要将 users 表作为主表,即使它们在 orders 表中没有匹配项也要显示出来,这正是 LEFT JOIN 的用途,对于 orders 表中可能为 NULL 的 username,使用 CASE WHEN 语句进行条件替换。
SQL 解答:
SELECT
u.user_id,
CASE
WHEN o.username IS NOT NULL THEN o.username
ELSE 'Inactive'
END AS username
FROM
users u
LEFT JOIN
orders o ON u.user_id = o.user_id
ORDER BY
u.user_id ASC;
解释:
FROM users u LEFT JOIN orders o ON u.user_id = o.user_id:将users表左连接到orders表,这样,users表中的所有记录都会被保留,orders表中如果没有匹配项,则对应字段为NULL。CASE WHEN o.username IS NOT NULL THEN o.username ELSE 'Inactive' END AS username:检查连接后的username是否为NULL,如果不是,说明用户下过单,使用其真实用户名;如果是NULL,说明用户没有下过单,显示为 "Inactive"。ORDER BY u.user_id ASC:按照user_id升序排列结果。
第三部分:操作系统与网络
4:Linux命令
描述:**
如何查看一个进程占用的CPU和内存情况?如何实时查看一个名为 nginx 的进程的CPU使用率变化?
考察点:
- Linux常用系统监控命令
- 对进程状态的理解
解答:
-
查看一个进程占用的CPU和内存情况: 最常用的命令是
ps和top。-
ps命令: 如果你想查看当前所有进程的CPU和内存占用,可以使用:ps aux
a: 显示所有进程u: 以用户为中心显示x: 显示没有控制终端的进程 输出结果中的%CPU和%MEM列分别表示进程的CPU和内存占用百分比。
如果你想查看特定PID(例如PID为1234)的进程:
ps -p 1234 -o pid,ppid,cmd,%cpu,%mem
-
top命令:top是一个动态的、交互式的进程查看器,它默认会按照CPU使用率降序排列进程,并实时刷新,你可以在top界面中直接看到%CPU和%MEM列。
-
-
实时查看名为
nginx的进程的CPU使用率变化: 可以结合ps和watch命令,或者直接使用top。-
使用
top(推荐):top -p $(pgrep nginx)
pgrep nginx会查找所有名为nginx的进程的PID,并返回这些PID组成的列表。top -p ...会只显示这些指定PID的进程信息,并实时刷新。
-
使用
ps和watch:watch -n 1 "ps -C nginx -o pid,user,%cpu,%mem --sort=-%cpu"
watch -n 1:每秒执行一次后面的命令。ps -C nginx:只显示名为nginx的进程。--sort=-%cpu:按照CPU使用率降序排列。
-
第四部分:系统设计
5:设计一个短链接服务
描述:**
请设计一个类似 t.cn 这样的短链接服务,当用户访问一个短链接时,服务需要将其重定向到原始的长链接。
考察点:
- 系统设计能力
- 架构思维
- 数据库设计
- 性能和扩展性考虑
解答思路: 这是一个典型的系统设计题,可以从以下几个方面展开:
功能需求分析
- 核心功能:
- 创建短链接: 用户输入一个长URL,服务生成一个唯一的短URL。
- 重定向: 当用户访问短URL时,服务返回一个HTTP 301(永久重定向)或302(临时重定向)响应,将浏览器引导至原始长URL。
- 附加功能(可选):
- 自定义短链接后缀。
- 查看短链接的访问统计信息(点击次数、访问时间、IP地址等)。
- 短链接的有效期设置。
API设计
- POST /shorten
- Request Body:
{ "long_url": "https://www.a-very-long-url.com/path/to/resource" } - Response Body:
{ "short_url": "http://short.ly/Ab3cd" }
- Request Body:
- GET /{short_key}
- 访问
http://short.ly/Ab3cd - Response: HTTP 301 Redirect to
https://www.a-very-long-url.com/path/to/resource
- 访问
数据库设计 这是设计的核心,我们需要存储长短URL的映射关系。
-
直接使用数据库自增ID
- 表结构:
CREATE TABLE `urls` ( `id` INT AUTO_INCREMENT PRIMARY KEY, `long_url` VARCHAR(2048) NOT NULL, `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX `idx_long_url` (`long_url`) );
- 生成短链接:
- 将长URL存入数据库,获得自增的
id。 - 将这个
id转换成62进制(包含0-9, a-z, A-Z)的字符串,作为short_key。12345->bZ。 - 组合成完整的短URL,如
http://short.ly/bZ。
- 将长URL存入数据库,获得自增的
- 重定向:
- 从请求中解析出
short_key(如bZ)。 - 将
short_key转换回10进制的id(如bZ->12345)。 - 用这个
id去数据库中查询long_url。 - 执行重定向。
- 从请求中解析出
- 优点:
id是唯一的,且自增,保证了short_key的唯一性,存储short_key的计算是O(1)的。 - 缺点: 如果想自定义短链接,此方案不适用。
- 表结构:
-
直接生成随机字符串
- 表结构:
CREATE TABLE `short_urls` ( `short_key` VARCHAR(10) PRIMARY KEY, `long_url` VARCHAR(2048) NOT NULL, `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP );
- 生成短链接:
- 生成一个足够长的随机字符串(如6-8位)作为
short_key。 - 检查
short_key是否已存在于数据库中(使用唯一索引防止冲突)。 - 如果冲突,重新生成;如果未冲突,将
(short_key, long_url)存入数据库。
- 生成一个足够长的随机字符串(如6-8位)作为
- 优点: 支持自定义短链接。
- 缺点: 存在哈希冲突的可能,需要处理重试逻辑,随着数据量增大,冲突概率增加,性能可能下降。
- 表结构:
性能与扩展性考虑
- 缓存: 短链接的“读”操作(重定向)远多于“写”操作(创建),必须使用缓存(如 Redis)来加速。
- 缓存策略: 当创建短链接时,除了写入数据库,也将
(short_key -> long_url)的映射关系存入Redis,当用户访问短链接时,先查询Redis,如果命中则直接返回,未命中再查询数据库,并将结果回填到缓存中。
- 缓存策略: 当创建短链接时,除了写入数据库,也将
- 高可用:
数据库和缓存都需要做主从复制或集群部署,避免单点故障。
- 负载均衡:
在短链接服务前部署负载均衡器(如Nginx, SLB),将流量分发到多个后端服务实例。
- 分布式ID生成(如果采用方案一):
如果系统是分布式的,数据库的自增ID可能成为瓶颈,可以考虑使用雪花算法(Snowflake)等分布式ID生成方案来代替数据库自增ID。
- 安全性:
- 需要对用户输入的长URL进行合法性校验,防止恶意链接。
- 可以对URL进行签名,防止用户篡改。
一个完整的短链接服务 = API接口 + 长短URL映射存储(DB) + 高速缓存 + 负载均衡 + 监控告警,面试时,清晰地画出架构图并阐述各个组件的作用是加分项。
