LeetCode 180-连续出现的数字

115 阅读3分钟

题目

表: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 是唯一连续出现至少三次的数字。

思路

解法一: 判断(id+1, num) and (id+2, num)是否在表中

当遍历到每一行的时候,判断(id+1, num)和(id+2, num)是否在表中,在的话,就select,可能存在大于3条的会有重复数据,所以还需要去重一次。

优点: 思路清晰明了,方便理解。

缺点: 如果题目要求判断连续出现10次或者100次,对于sql的编写会异常庞大和不便。

select distinc num as ConsecutiveNums
from 
  Logs 
where 
    (id+1, num) in (select * from Logs)
and
    (id+2, num) in (select * from Logs);

注意: 在判断(id+1, num)和(id+2,num)时,应该在对logs表的数据线进行查询,然后进行in查询,而不是直接写Logs。

解法二: 2次JOIN连表

将同一张表通过2次连接,形成3张表,l1\l2\l3,其中连接条件为 l1\l2\l3的ID保持连续,不妨设为递减。然后查询出l1.num=l2.num=l3.num的li.num值,最后对num去重即可。

优点: 思路简单,连表方便理解。

缺点: 和解法一一样拓展性差,过多连表的话也可能性能有一定下降。

select distinct l1.num as ConsecutiveNums
from logs l1
left join logs l2 on l1.id = l2.id + 1
left join logs l3 on l2.id = l3.id + 1
where l1.num = l2.num and l2.num = l3.num;

解法三: 变量+case when判断

梳理题目条件:1、id必须连续 2、num必须相等 3 、count(满足1和2的行数)>=3 不难分析得出,在进行SQL查询的时候,需要判断:

  • 条件a: 上一行ID,不妨设为preId 和 当前ID是否满足 preId+1 == Id;
  • 条件b: 上一行Num,不妨设为preNum 和 当前Num是否满足 preNum == Num

当且仅当(a is True) && (b is True)时,计数++

那么当条件a和条件b都不满足或者说部分满足的情况呢?

不论是a,b均不满足,或者是部分不满足的情况,说明当前这行已经是新一行了,应该作为新的判断标准,更新preId 和 preNum 的值,且该行第一次出现,所以count初始化为1。

如何保存count的值用于后续判断呢?

可以新增一列,统计第i行在[1,i]行中出现的连续次数,记为count.

解决方案

  • 设置3个变量
  • 判断变量 preId+1 == Id and preNum == Num
    • 如果条件满足,count++
    • 如果不满足,count=1
  • 更新 preId 的值,更新preNum的值

在了解了sql变量语法后,sql如下:

select distinct num as ConsecutiveNums
from (
    select @count := case
        when @preId+1 = id and @preNum = num then @count := @count + 1
        else @count := 1
        end as CNT,
        @preId := id,
        @preNum := num,
        num
    from (select * from logs order by id) as t1, 
    -- 声明变量
    (select @preId := null, @preNum := null, @count := null) as t2
) as temp
where temp.CNT >= 3;

注意:之前参照一位大佬的解答,一直想加入一个@preId变量在case when里,还想再then中完成更新,没做出来,花费很多时间,后面发现这样写其实更清楚明了,每一行新增@count变量计算连续值,然后更新@preId和@preNum。

优点: 对于问题的扩展性很好,可以延伸到连续出现N次、不要求ID连续的连续出现次数(去除掉preId判断即可)

缺点: 需要了解sql的变量声明 赋值 case when,已经语句之间的连接和查询,较为复杂。