180. 连续出现的数字(Consecutive Numbers)

难度:中等 | 考点:自连接、窗口函数 LEAD/LAG

题目

Logs 表(Id, Num),找出所有至少连续出现三次的数字。

示例

 
Logs 表:
 
| id | num |
 
| 1  | 1   |
 
| 2  | 1   |
 
| 3  | 1   |
 
| 4  | 2   |
 
| 5  | 1   |
 
| 6  | 2   |
 
| 7  | 2   |
 
输出:
 
| ConsecutiveNums |
 
| 1               |
 

思路

先讲个故事:排队报数

想象一群士兵排队报数。班长想知道”谁连续报了三次一样的数?“最直观的方法是:让三个人站一排(三表自连接),检查他们报的数是不是一样的。更聪明的方法是:每个人往后看两个人(LEAD 函数),如果三个人报的数一样,就记下来。

引导式推导:两种解法

解法一:自连接(直观)

把表自己连三次,分别代表”第一行”、“第二行”、“第三行”:


graph LR

    L1["Logs l1<br/>第一行"] -->|"Id 连续<br/>Num 相同"| L2["Logs l2<br/>第二行"]

    L2 -->|"Id 连续<br/>Num 相同"| L3["Logs l3<br/>第三行"]

    L1 --> R["结果: ConsecutiveNums"]

解法二:窗口函数 LEAD(推荐)

用 LEAD 函数取”下一行”和”下两行”的 Num:


graph LR

    A["当前行 Num"] --> B{"Num == next1<br/>且 Num == next2?"}

    B -->|"是"| C["连续三次 ✓"]

    B -->|"否"| D["不连续"]

自连接 vs LEAD

自连接LEAD 窗口函数
时间O(n³)O(n log n)
依赖 Id 连续✓(Id 有间隙会失效)✗(更健壮)
推荐兜底方案

SQL 代码

方法一:自连接(直观易懂)

 
SELECT DISTINCT l1.Num AS ConsecutiveNums
 
FROM Logs l1, Logs l2, Logs l3
 
WHERE l1.Id = l2.Id - 1
 
  AND l2.Id = l3.Id - 1
 
  AND l1.Num = l2.Num
 
  AND l2.Num = l3.Num;
 

方法二:窗口函数 LEAD(推荐)

 
SELECT DISTINCT Num AS ConsecutiveNums
 
FROM (
 
    SELECT
 
        Num,
 
        LEAD(Num, 1) OVER (ORDER BY Id) AS next1,
 
        LEAD(Num, 2) OVER (ORDER BY Id) AS next2
 
    FROM Logs
 
) t
 
WHERE Num = next1 AND Num = next2;
 

复杂度

方法时间复杂度空间复杂度
自连接(三次)O(n³) — 笛卡尔积O(n²) — 中间结果
LEAD 窗口函数O(n log n) — 排序主导O(n) — 子查询临时存储

实战考量

频率分析

出现在:窗口函数应用题,约 25% 的同类题会考。关键在于你LEAD/LAG 函数的理解,以及对”连续”问题的建模能力。

延伸思考

Q:自连接 vs 窗口函数哪个好?

A:自连接更直观,但依赖 Id 连续,如果 Id 有间隙(如删过行)会失效。窗口函数不依赖 Id 连续性,更健壮,推荐。

Q:如果 Id 不连续怎么办?

A:用窗口函数 LEAD(见方法二),或者先用 ROW_NUMBER() OVER (ORDER BY Id) 生成连续行号。

Q:如果要找连续出现 N 次?

A:自连接需要 N 次(不优雅)。窗口函数可以扩展:LEAD(Num, 1), LEAD(Num, 2), ..., LEAD(Num, N-1),然后判断所有 LEAD 值相等。

Q:LEAD 和 LAG 的区别?

A:LEAD 取后面的行(向前看),LAG 取前面的行(向后看)。LEAD(col, 2) = 取后面第 2 行的值;LAG(col, 2) = 取前面第 2 行的值。本题用 LEAD 和 LAG 都可以。

Q:LEAD/LAG 的第三个参数是什么?

A:LEAD(Num, 1, 0) 的第三个参数是默认值,当取不到值时(如最后一行没有下一行)返回该默认值。默认是 NULL。

易错点

  • 忘记 DISTINCT,同一个数字被多次输出

  • 自连接时 Id 条件写错(如写反方向)

  • = 连接而不是笛卡尔积 + WHERE(隐式连接)


生活类比

连续出现的数字 → 排队报数查重复

班长想知道”谁连续报了三次一样的数?“最直观的方法是让三个人站一排检查。更聪明的方法是每个人往后看两个人,如果三个人报的数一样就记下来。

一句话总结:当前行 = 下一行 = 下两行 → 连续三次相同。


相关题目

题目关系
178分数排名窗口函数 DENSE_RANK
184部门工资最高的员工窗口函数 PARTITION BY

→ 返回题单:LeetCode学习路线图 > 十六、SQL 高频

速记卡(面试闪卡)

Q1:一句话讲清「180. 连续出现的数字(Consecutive Numbers)」到底是什么?

A:在 Logs 表找至少连续出现三次的数字,用窗口函数 LEAD(或三次自连接),判断当前行=下一行=下两行。

Q2:士兵报数查重复(LEAD/LAG) —— 怎么理解?

A:类比:班长查”谁连报三次一样的数”。笨办法让三人站一排(三次自连接)比对;聪明办法每人往后看两人(LEAD 函数),三人同数就记下。就像排队点名,隔一个瞅一眼。(Look ahead)

Q3:自连接 vs LEAD(self-join vs window) —— 怎么理解?

A:类比:自连接:Logs 连三次,WHERE l1.Id=l2.Id-1 且 Num 相等,直观但 O(n³)。LEAD 推荐:子查询取 LEAD(Num,1)、LEAD(Num,2),WHERE Num=next1 AND Num=next2,O(n log n) 更健壮。(Window beats join)

Q4:复杂度与健壮性(O(n log n) vs O(n³)) —— 怎么理解?

A:类比:自连接是笛卡尔积 O(n³)、还依赖 Id 连续(删过行就崩);LEAD 窗口函数 O(n log n) 不依赖 Id 连续性,更稳。找连续 N 次只需多取几个 LEAD,自连接得连 N 次,丑。(Robust to gaps)

Q5:易错与变体(DISTINCT, ROW_NUMBER) —— 怎么理解?

A:类比:别忘了 DISTINCT,否则同数字被重复输出;Id 不连续就用 ROW_NUMBER() 先造连续行号。LEAD 第三参是取不到时的默认值(默认 NULL)。LAG 往前看、LEAD 往后看,本题都能用。(Watch defaults)

Q6:核心速记主线有哪些?

  • 题目:找至少连续出现三次的数字

  • 思路:LEAD 看后两行,三行同数即连续(window function)

  • 代码:自连接直观但慢;LEAD 推荐

  • 复杂度:自连接 O(n³)、LEAD O(n log n)

  • 实战:记得 DISTINCT;Id 不连续用 ROW_NUMBER

口诀

A:连续数字藏表底,

LEAD 后看两行起;

三同记下莫重复,

窗口函数胜自比。

相关链接