CONTENTS
- LeetCode 176. 第二高的薪水(SQL 中等)
- LeetCode 177. 第 N 高的薪水(SQL 中等)
- LeetCode 178. 分數排名(SQL 中等)
- LeetCode 179. 最大數(中等)
- LeetCode 180. 連續出現的數字(SQL 中等)
LeetCode 176. 第二高的薪水(SQL 中等)
【題目描述】
Employee
表:
+-------------+------+
| Column Name | Type |
+-------------+------+
| id | int |
| salary | int |
+-------------+------+
id 是這個表的主鍵。
表的每一行包含員工的工資信息。
查詢并返回 Employee
表中第二高的不同薪水。如果不存在第二高的薪水,查詢應該返回 null
(Pandas 則返回 None
)。
查詢結果如下例所示。
【示例 1】
輸入:
Employee 表:
+----+--------+
| id | salary |
+----+--------+
| 1 | 100 |
| 2 | 200 |
| 3 | 300 |
+----+--------+輸出:
+---------------------+
| SecondHighestSalary |
+---------------------+
| 200 |
+---------------------+
【示例 2】
輸入:
Employee 表:
+----+--------+
| id | salary |
+----+--------+
| 1 | 100 |
+----+--------+輸出:
+---------------------+
| SecondHighestSalary |
+---------------------+
| null |
+---------------------+
【分析】
本題有多種解法:
- 方法一:可以先從
Employee
表中查詢Salary
列,并且使用DISTINCT
關鍵字去除重復的工資值。然后使用ORDER BY Salary DESC
將查詢結果按照Salary
列的值進行降序排列,接著使用LIMIT 1 OFFSET 1
跳過 1 行,并限制輸出結果只有 1 行,也就是只輸出第二行。然而,如果沒有第二高的薪資,即表里可能只有一條記錄,這種情況下查詢會返回一個空結果集,而不是NULL
,因此可以使用子查詢,如果子查詢沒有返回任何值,外層的SELECT
會將結果視為NULL
,因此,整個查詢會返回NULL
。 - 方法二:先使用
SELECT MAX(Salary) FROM Employee
查詢表中的最大工資值,然后將小于最大工資作為查詢條件再查一遍表,然后返回查詢結果的最大值就是整個表中第二大的值。
【代碼】
【方法一】
# Write your MySQL query statement below
SELECT (SELECT DISTINCT SalaryFROM EmployeeORDER BY Salary DESCLIMIT 1 OFFSET 1
) AS SecondHighestSalary;
【方法二】
# Write your MySQL query statement below
SELECT MAX(Salary) AS SecondHighestSalary
FROM Employee
WHERE Salary < (SELECT MAX(Salary) FROM Employee);
LeetCode 177. 第 N 高的薪水(SQL 中等)
【題目描述】
表:Employee
+-------------+------+
| Column Name | Type |
+-------------+------+
| id | int |
| salary | int |
+-------------+------+
id 是該表的主鍵(列中的值互不相同)。
該表的每一行都包含有關員工工資的信息。
編寫一個解決方案查詢 Employee
表中第 n n n 高的不同工資。如果少于 n n n 個不同工資,查詢結果應該為 null
。
查詢結果格式如下所示。
【示例 1】
輸入:
Employee table:
+----+--------+
| id | salary |
+----+--------+
| 1 | 100 |
| 2 | 200 |
| 3 | 300 |
+----+--------+
n = 2輸出:
+------------------------+
| getNthHighestSalary(2) |
+------------------------+
| 200 |
+------------------------+
【示例 2】
輸入:
Employee 表:
+----+--------+
| id | salary |
+----+--------+
| 1 | 100 |
+----+--------+
n = 2輸出:
+------------------------+
| getNthHighestSalary(2) |
+------------------------+
| null |
+------------------------+
【分析】
與上一題類似,直接使用第一種方法將偏移量設置為 n ? 1 n - 1 n?1 即可,需要注意的是 LIMIT
語句中不能有表達式,要先把 n ? 1 n - 1 n?1 計算出來。
也可以使用窗口函數(Window Function)DENSE_RANK()
為工資值按降序排列計算排名。DENSE_RANK()
函數為相同值分配相同的排名,并且不會跳過后續的排名。然后使用 WHERE ranking = N
來過濾數據,保留排名等于傳入參數 N
的行,該方法也適用于上一題。
OVER
關鍵字是 SQL 中用于定義窗口函數的作用范圍和行為的關鍵字,它允許你在一個查詢中對一組相關行進行計算,而無需像聚合函數那樣將它們分組到一個單獨的行中。
【代碼】
【方法一】
CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGINDECLARE M INT;SET M = N - 1;RETURN (# Write your MySQL query statement below.SELECT (SELECT DISTINCT SalaryFROM EmployeeORDER BY Salary DESCLIMIT 1 OFFSET M));
END
【方法二】
CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGINRETURN (# Write your MySQL query statement below.SELECT IF(COUNT(*), RKSalary.Salary, NULL) # count(*) 計算查詢結果的數量,若不為空則返回 Salary 列,否則返回 NULLFROM (SELECT Salary, DENSE_RANK() OVER(ORDER BY Salary DESC) as RKFROM Employee) AS RKSalary # 子查詢生成的派生表必須有一個別名WHERE RKSalary.RK = N);
END
LeetCode 178. 分數排名(SQL 中等)
【題目描述】
表:Scores
+-------------+---------+
| Column Name | Type |
+-------------+---------+
| id | int |
| score | decimal |
+-------------+---------+
id 是該表的主鍵(有不同值的列)。
該表的每一行都包含了一場比賽的分數。score 是一個有兩位小數點的浮點值。
編寫一個解決方案來查詢分數的排名。排名按以下規則計算:
- 分數應按從高到低排列。
- 如果兩個分數相等,那么兩個分數的排名應該相同。
- 在排名相同的分數后,排名數應該是下一個連續的整數。換句話說,排名之間不應該有空缺的數字。
按 score
降序返回結果表。
查詢結果格式如下所示。
【示例 1】
Scores 表:
+----+-------+
| id | score |
+----+-------+
| 1 | 3.50 |
| 2 | 3.65 |
| 3 | 4.00 |
| 4 | 3.85 |
| 5 | 4.00 |
| 6 | 3.65 |
+----+-------+輸出:
+-------+------+
| score | rank |
+-------+------+
| 4.00 | 1 |
| 4.00 | 1 |
| 3.85 | 2 |
| 3.65 | 3 |
| 3.65 | 3 |
| 3.50 | 4 |
+-------+------+
【分析】
上一題中用到的窗口函數 DENSE_RANK()
正適合用來解決本題。此外還有另一種思路,每個分數的排名就是表經過去重后大于等于當前分數的數量,例如對于樣例中的 3.85,去重后大于當前數的值為 4.00 和 3.85,即當前數的排名為 2。
【代碼】
【方法一】
# Write your MySQL query statement below
SELECT score, DENSE_RANK() OVER(ORDER BY score DESC) AS "rank" # rank 是關鍵字,因此需要用引號
FROM Scores;
【方法二】
# Write your MySQL query statement below
SELECT S1.score, (SELECT COUNT(DISTINCT S2.score)FROM Scores S2WHERE S2.score >= S1.score
) AS "rank"
FROM Scores S1
ORDER BY S1.score DESC;
LeetCode 179. 最大數(中等)
【題目描述】
給定一組非負整數 nums
,重新排列每個數的順序(每個數不可拆分)使之組成一個最大的整數。
注意:輸出結果可能非常大,所以你需要返回一個字符串而不是整數。
【示例 1】
輸入:nums = [10,2]
輸出:"210"
【示例 2】
輸入:nums = [3,30,34,5,9]
輸出:"9534330"
【提示】
1 < = n u m s . l e n g t h < = 100 1 <= nums.length <= 100 1<=nums.length<=100
0 < = n u m s [ i ] < = 1 0 9 0 <= nums[i] <= 10^9 0<=nums[i]<=109
【分析】
本題的思路也比較難想,需要定義一種新的比較方式:
- a = b ? a b = b a a = b \iff ab = ba a=b?ab=ba
- a ≤ b ? a b ≤ b a a \le b \iff ab \le ba a≤b?ab≤ba
- a ≥ b ? a b ≥ b a a \ge b \iff ab \ge ba a≥b?ab≥ba
例如我們現在有兩個數 123 和 45,由于 12345 < 45123 12345 < 45123 12345<45123,因此 123 < 45 123 < 45 123<45。
假設最大的整數表示為: s 1 s 2 s 3 … s n s_1s_2s_3\dots s_n s1?s2?s3?…sn?,那么說明該排序算法從大到小排序后的結果為: s 1 ≥ s 2 ≥ s 3 ≥ ? ≥ s n s_1 \ge s_2 \ge s_3 \ge \dots \ge s_n s1?≥s2?≥s3?≥?≥sn?。若存在 s i < s i + 1 s_i < s_{i + 1} si?<si+1? 的情況,說明 s i s i + 1 < s i + 1 s i s_is_{i + 1} < s_{i + 1}s_i si?si+1?<si+1?si?,而當前數為最大整數,因此產生了沖突,說明排序算法從大到小排序后的結果一定是最大整數。
到這里還沒有完全結束,因為這個比較方式是我們自己定義的,還需要證明這個方式能夠正確排序,例如不能出現 a < b a < b a<b、 b < c b < c b<c、 c < a c < a c<a 的情況。這是離散數學中的一個概念,一個能夠排序的關系被稱為全序關系,需要滿足以下三個性質:
- 若 a ≤ b a \le b a≤b 且 b ≤ a b \le a b≤a,則 a = b a = b a=b(反對稱性);
- 若 a ≤ b a \le b a≤b 且 b ≤ c b \le c b≤c,則 a ≤ c a \le c a≤c(傳遞性);
- a ≤ b a \le b a≤b 或 b ≤ a b \le a b≤a(完全性)。
我們證明一下這三個性質:
- 若 a b ≤ b a ab \le ba ab≤ba 且 b a ≤ a b ba \le ab ba≤ab,而其中的比較方式為具有全序關系的字典序比較,因此能推出 a b = b a ab = ba ab=ba;
- 若 a b ≤ b a ab \le ba ab≤ba 且 b c ≤ c b bc \le cb bc≤cb,假設 a , b , c a, b, c a,b,c 的長度分別為 x , y , z x, y, z x,y,z,因此 a b ab ab 的長度等于 b a ba ba,字典序也就可以轉化為普通整數的比較,即 a ? 1 0 y + b ≤ b ? 1 0 x + a ? a ( 1 0 y ? 1 ) ≤ b ( 1 0 x ? 1 ) ? a b ≤ ( 1 0 x ? 1 ) ( 1 0 y ? 1 ) a * 10^y + b \le b * 10^x + a \Rightarrow a(10^y - 1) \le b(10^x - 1) \Rightarrow \frac{a}{b} \le \frac{(10^x - 1)}{(10^y - 1)} a?10y+b≤b?10x+a?a(10y?1)≤b(10x?1)?ba?≤(10y?1)(10x?1)?,同理 b c ≤ ( 1 0 y ? 1 ) ( 1 0 z ? 1 ) \frac{b}{c} \le \frac{(10^y - 1)}{(10^z - 1)} cb?≤(10z?1)(10y?1)?,將兩個不等式的左右兩邊分別同時相乘可以得到 a c ≤ ( 1 0 x ? 1 ) ( 1 0 z ? 1 ) \frac{a}{c} \le \frac{(10^x - 1)}{(10^z - 1)} ca?≤(10z?1)(10x?1)?,這個式子就表示 a c ≤ c a ac \le ca ac≤ca;
- 顯然任何的 a b ab ab 和 b a ba ba 之間都能進行字典序比較,滿足完全性。
【代碼】
class Solution {
public:string largestNumber(vector<int>& nums) {sort(nums.begin(), nums.end(), [](int a, int b){string sa = to_string(a), sb = to_string(b);return sa + sb > sb + sa;});if (nums[0] == 0) return "0"; // 如果全為 0 那么結果只保留一個 0string res;for (int x: nums) res += to_string(x);return res;}
};
LeetCode 180. 連續出現的數字(SQL 中等)
【題目描述】
表:Logs
+-------------+---------+
| Column Name | Type |
+-------------+---------+
| id | int |
| num | varchar |
+-------------+---------+
在 SQL 中,id 是該表的主鍵。
id 是一個自增列。
找出所有至少連續出現三次的數字。
返回的結果表中的數據可以按任意順序排列。
結果格式如下面的例子所示:
【示例 1】
輸入:
Logs 表:
+----+-----+
| id | num |
+----+-----+
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 2 |
| 5 | 1 |
| 6 | 2 |
| 7 | 2 |
+----+-----+輸出:
Result 表:
+-----------------+
| ConsecutiveNums |
+-----------------+
| 1 |
+-----------------+解釋:1 是唯一連續出現至少三次的數字。
【分析】
最簡單的方式就是直接查詢三個 Logs
表,在 WHERE
語句中判斷這三行的 id
是否連續且 num
相等。
此外還可以使用窗口函數 ROW_NUMBER()
為每個分區內的行分配唯一的行號,即使用 CAST(ROW_NUMBER() OVER(PARTITION BY num ORDER BY id) AS SIGNED) AS rn
語句將數據按 num
值分區,使得每個 num
值都有自己的計數序列,接著在每個分區內,按 id
值的升序為行分配行號:
+----+-----+ +----+-----+----+
| id | num | | id | num | rn |
+----+-----+ +----+-----+----+
| 1 | 1 | | 1 | 1 | 1 |
| 2 | 1 | | 2 | 1 | 2 |
| 3 | 1 | => | 3 | 1 | 3 |
| 4 | 2 | | 5 | 1 | 4 |
| 5 | 1 | | 4 | 2 | 1 |
| 6 | 2 | | 6 | 2 | 2 |
| 7 | 2 | | 7 | 2 | 3 |
+----+-----+ +----+-----+----+
CAST(... AS SIGNED)
將 ROW_NUMBER()
的結果轉換為有符號整數類型。這是因為 ROW_NUMBER()
返回的是 BIGINT UNSIGNED
類型,而后續的計算需要使用有符號整數。
接下來使用 GROUP BY num, rn - id
語句將查詢結果按 num
列以及 rn - id
的計算結果進行分組。目的是識別和組合具有相同 num
值且連續的 id
值的行,因為如果 id
連續,rn - id
的值會相同:
+----+-----+----+---------+
| id | num | rn | rn - id |
+----+-----+----+---------+
| 1 | 1 | 1 | 0 |
| 2 | 1 | 2 | 0 |
| 3 | 1 | 3 | 0 |
| 5 | 1 | 4 | -1 |
| 4 | 2 | 1 | -3 |
| 6 | 2 | 2 | -4 |
| 7 | 2 | 3 | -4 |
+----+-----+----+---------+
最后使用 HAVING COUNT(num) >= 3
語句對 GROUP BY
生成的分組結果進行過濾,只保留滿足連續出現至少 3 次相同 num
的組。
【代碼】
【方法一】
# Write your MySQL query statement below
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;
【方法二】
# Write your MySQL query statement below
SELECT DISTINCT num AS ConsecutiveNums
FROM (SELECT id, num, CAST(ROW_NUMBER() OVER(PARTITION BY num ORDER BY id) AS SIGNED) AS rnFROM Logs
) AS Logs_RN
GROUP BY Logs_RN.num, Logs_RN.rn - Logs_RN.id
HAVING COUNT(num) >= 3;