一、問題描述
給定一個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 prev2FROM 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 prev2FROM 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 prev4FROM 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
字段添加索引,提升窗口函數性能。
八、總結
-
核心邏輯:通過比較當前行與前兩行的值,判斷是否連續相同。
-
關鍵技巧:
-
自連接法:適用于所有數據庫版本
-
窗口函數法:簡潔高效,推薦使用
-
-
擴展應用:
-
動態處理任意連續次數
-
處理更復雜的業務場景(如連續登錄天數)
-
感謝各位的閱讀,后續將持續給大家講解力扣中的算法題和數據庫題,如果覺得這篇內容對你有幫助,別忘了點贊和關注,后續還有更多精彩的算法解析與你分享!