【LeetCode 题解】数据库:180. 连续出现的数字
一、问题描述
给定一个Logs
表,包含自增 ID 和数字字段:
CREATE TABLE Logs (
id INT PRIMARY KEY AUTO_INCREMENT,
num VARCHAR(50)
);
要求编写 SQL 查询,找出所有至少连续出现三次的数字。例如:
+----+-----+
| id | num |
+----+-----+
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 2 |
+----+-----+
输出应为:
+-----------------+
| ConsecutiveNums |
+-----------------+
| 1 |
+-----------------+
二、核心思路解析
问题本质
需要识别表中连续出现至少三次的相同数字。关键点在于:
-
连续条件:相邻行的
num
值相同 -
次数统计:连续次数≥3 次
-
去重需求:相同数字只需返回一次
关键技术点
1. 如何判断连续?
-
自连接法:通过表自连接比较相邻行
-
窗口函数法:使用
LAG()
获取前一行数据
2. 如何统计连续次数?
-
滑动窗口:检查当前行与前两行是否相同
-
状态标记:用变量记录当前连续状态
三、解决方案详解
方法一:自连接法
实现逻辑
通过三次自连接,将当前行与前两行进行比较:
SELECT DISTINCT a.num AS ConsecutiveNums
FROM Logs a
JOIN Logs b ON a.id = b.id + 1
JOIN Logs c ON a.id = c.id + 2
WHERE a.num = b.num AND b.num = c.num;
执行流程
以示例数据为例:
-
连接条件:
a.id = b.id + 1
→ 当前行与前一行a.id = c.id + 2
→ 当前行与前两行
-
过滤条件:三个连续行的
num
相同 -
去重处理:使用
DISTINCT
避免重复结果
方法二:窗口函数法(推荐)
实现逻辑
使用LAG()
窗口函数获取前两行数据:
SELECT DISTINCT num AS ConsecutiveNums
FROM (
SELECT
num,
LAG(num, 1) OVER (ORDER BY id) AS prev1,
LAG(num, 2) OVER (ORDER BY id) AS prev2
FROM Logs
) AS sub
WHERE num = prev1 AND prev1 = prev2;
执行流程
-
子查询:
LAG(num, 1)
获取前一行的num
LAG(num, 2)
获取前两行的num
-
过滤条件:当前行与前两行
num
相同 -
去重处理:使用
DISTINCT
确保唯一结果
四、两种方法对比
维度 | 自连接法 | 窗口函数法 |
---|---|---|
代码复杂度 | ★★★☆☆ | ★★☆☆☆ |
执行效率 | 低(需三次表扫描) | 高(单次扫描) |
可读性 | 较低(需理解自连接逻辑) | 高(直观的前向引用) |
适用场景 | 旧版本数据库(如 MySQL 5.x) | 现代数据库(MySQL 8.0+) |
推荐场景:
-
优先使用窗口函数法(简洁高效)
-
若数据库不支持窗口函数,使用自连接法
五、扩展优化
动态处理任意连续次数
将固定的 3 次改为变量N
:
CREATE FUNCTION getConsecutiveNums(N INT) RETURNS TABLE
RETURN
SELECT DISTINCT num AS ConsecutiveNums
FROM (
SELECT
num,
LAG(num, 1) OVER (ORDER BY id) AS prev1,
LAG(num, 2) OVER (ORDER BY id) AS prev2
FROM Logs
) AS sub
WHERE num = prev1 AND prev1 = prev2;
处理更长连续次数
对于连续 5 次的情况,只需增加更多LAG()
调用:
SELECT DISTINCT num
FROM (
SELECT
num,
LAG(num, 1) OVER (ORDER BY id) AS prev1,
LAG(num, 2) OVER (ORDER BY id) AS prev2,
LAG(num, 3) OVER (ORDER BY id) AS prev3,
LAG(num, 4) OVER (ORDER BY id) AS prev4
FROM Logs
) AS sub
WHERE num = prev1 AND prev1 = prev2
AND prev2 = prev3 AND prev3 = prev4;
六、测试用例验证
测试用例 1:正常情况
输入:
INSERT INTO Logs (num) VALUES
('1'),('1'),('1'),('2'),('1'),('2'),('2');
预期输出:
+-----------------+
| ConsecutiveNums |
+-----------------+
| 1 |
+-----------------+
测试用例 2:多个连续数字
输入:
INSERT INTO Logs (num) VALUES
('5'),('5'),('5'),('5'),('3'),('3'),('3');
预期输出:
+-----------------+
| ConsecutiveNums |
+-----------------+
| 5 |
| 3 |
+-----------------+
测试用例 3:边界情况
输入:
INSERT INTO Logs (num) VALUES
('a'),('a'),('a'),('a');
预期输出:
+-----------------+
| ConsecutiveNums |
+-----------------+
| a |
+-----------------+
七、常见问题解答
Q:为什么需要使用DISTINCT
?
A:防止同一数字多次出现在结果中(例如连续 4 次会产生两条记录)。
Q:如果连续次数超过 3 次会怎样?
A:会被正确识别,因为只要存在至少连续 3 次即可。
Q:如何处理非常大的表?
A:为id
字段添加索引,提升窗口函数性能。
八、总结
-
核心逻辑:通过比较当前行与前两行的值,判断是否连续相同。
-
关键技巧:
-
自连接法:适用于所有数据库版本
-
窗口函数法:简洁高效,推荐使用
-
-
扩展应用:
-
动态处理任意连续次数
-
处理更复杂的业务场景(如连续登录天数)
-
感谢各位的阅读,后续将持续给大家讲解力扣中的算法题和数据库题,如果觉得这篇内容对你有帮助,别忘了点赞和关注,后续还有更多精彩的算法解析与你分享!