【连续签到领金币】解题思路总结

连续签到领金币

描述
用户行为日志表tb_user_log

在这里插入图片描述

(uid-用户ID, artical_id-文章ID, in_time-进入时间, out_time-离开时间, sign_in-是否签到)

场景逻辑说明:
artical_id-文章ID代表用户浏览的文章的ID,特殊情况artical_id-文章ID为0表示用户在非文章内容页(比如App内的列表页、活动页等)。注意:只有artical_id为0时sign_in值才有效。
从2021年7月7日0点开始,用户每天签到可以领1金币,并可以开始累积签到天数,连续签到的第3、7天分别可额外领2、6金币。
每连续签到7天后重新累积签到天数(即重置签到天数:连续第8天签到时记为新的一轮签到的第一天,领1金币)
问题:计算每个用户2021年7月以来每月获得的金币数(该活动到10月底结束,11月1日开始的签到不再获得金币)。结果按月份、ID升序排序。

注:如果签到记录的in_time-进入时间和out_time-离开时间跨天了,也只记作in_time对应的日期签到了。

DROP TABLE IF EXISTS tb_user_log;
CREATE TABLE tb_user_log (
    id INT PRIMARY KEY AUTO_INCREMENT COMMENT '自增ID',
    uid INT NOT NULL COMMENT '用户ID',
    artical_id INT NOT NULL COMMENT '视频ID',
    in_time datetime COMMENT '进入时间',
    out_time datetime COMMENT '离开时间',
    sign_in TINYINT DEFAULT 0 COMMENT '是否签到'
) CHARACTER SET utf8 COLLATE utf8_bin;

INSERT INTO tb_user_log(uid, artical_id, in_time, out_time, sign_in) VALUES
  (101, 0, '2021-07-07 10:00:00', '2021-07-07 10:00:09', 1),
  (101, 0, '2021-07-08 10:00:00', '2021-07-08 10:00:09', 1),
  (101, 0, '2021-07-09 10:00:00', '2021-07-09 10:00:42', 1),
  (101, 0, '2021-07-10 10:00:00', '2021-07-10 10:00:09', 1),
  (101, 0, '2021-07-11 23:59:55', '2021-07-11 23:59:59', 1),
  (101, 0, '2021-07-12 10:00:28', '2021-07-12 10:00:50', 1),
  (101, 0, '2021-07-13 10:00:28', '2021-07-13 10:00:50', 1),
  (102, 0, '2021-10-01 10:00:28', '2021-10-01 10:00:50', 1),
  (102, 0, '2021-10-02 10:00:01', '2021-10-02 10:01:50', 1),
  (102, 0, '2021-10-03 11:00:55', '2021-10-03 11:00:59', 1),
  (102, 0, '2021-10-04 11:00:45', '2021-10-04 11:00:55', 0),
  (102, 0, '2021-10-05 11:00:53', '2021-10-05 11:00:59', 1),
  (102, 0, '2021-10-06 11:00:45', '2021-10-06 11:00:55', 1);

题目分析

比较好理解的思考方式是通过根据需要的结果,一步一步反推自己需要什么的格式的数据

  1. 要求活动期间的签到获得的金币总数,那我最希望的是能够获得每一天用户签到时获得的金币数,然后只需要按照ID和month分组,sum一下就可以,如图

请添加图片描述

  1. 再反推,想要获得每一天用户签到时获得的金币数,那么我必须知道,用户当天签到是连续签到的第几天,得到天数以后很简单了,用case when 将天数 % 7 ,看余数。 余数是3 ,当天获得 3枚。余数是 0 ,当天获得7枚 。其他为 1 枚 。如图

请添加图片描述

  1. 推到这里那其实思路已经清晰了,求连续签到的天数,那无非就是连续问题

    1. 连续问题核心就是利用排序编号与签到日期的差值是相等的。因为如果是连续的话,编号也是自增1,日期也是自增1。

    2. 如图,***dt***是签到日期,***dt_tmp***是编号和签到日期的差值。可以发现 编号 8 是断了连续签到的,所以***dt_tmp***与前面的不相同

      请添加图片描述

  2. 那么再以dt_tmp和 uid 来分组,再***dense_rank*** 一次,就可以获得连续签到的天数了。那么问题就解决了。

    请添加图片描述

完整SQL

WITH t1 AS( -- t1表筛选出活动期间内的数据,并且为了防止一天有多次签到活动,distinct 去重
	SELECT
		DISTINCT uid,
		DATE(in_time) dt,
		DENSE_RANK() over(PARTITION BY uid ORDER BY DATE(in_time)) rn -- 编号
	FROM
		tb_user_log
	WHERE
		DATE(in_time) BETWEEN '2021-07-07' AND '2021-10-31' AND artical_id = 0 AND sign_in = 1
),
t2 AS (
	SELECT
	*,
	DATE_SUB(dt,INTERVAL rn day) dt_tmp, 
	case DENSE_RANK() over(PARTITION BY DATE_SUB(dt,INTERVAL rn day),uid ORDER BY dt )%7 -- 再次编号
		WHEN 3 THEN 3
		WHEN 0 THEN 7
		ELSE 1
	END as day_coin -- 用户当天签到时应该获得的金币数
	FROM
	t1
)
	SELECT
		uid,DATE_FORMAT(dt,'%Y%m') `month`, sum(day_coin) coin  -- 总金币数
	FROM
		t2
	GROUP BY
		uid,DATE_FORMAT(dt,'%Y%m')
	ORDER BY
		DATE_FORMAT(dt,'%Y%m'),uid;
1. C语言与7无关的数:这个问题其实是一个小数学题,我们可以通过枚举法求解。假设这个数为n,我们可以从1开始逐个判,如果一个数i既不是7的倍数,也不包含数字7,那么就可以认为它是与7无关的数。代码实现如下: ```c #include <stdio.h> int main() { int n; printf("请输入一个正整数:"); scanf("%d", &n); for (int i = 1; i <= n; i++) { if (i % 7 != 0 && i / 10 != 7 && i % 10 != 7) { printf("%d ", i); } } return 0; } ``` 2. 金币:这个问题其实是一个古老的谜题,又称为“假币问题”。假设有一堆金币,其中有一枚是假的,并且假币比真币轻。现在给你一架天平,问最少需要称多少次,才能找到那枚假币。答案是三次。具体做法如下: - 第一次将金币分成三堆,分别称两堆,如果两堆重量相等,说明假币在第三堆中;如果两堆重量不等,说明假币在较轻的一堆中。 - 第二次将较轻的那堆金币分成两堆,分别称两堆,如果两堆重量相等,说明假币在剩余的那堆中;如果两堆重量不等,说明假币在较轻的那堆中。 - 第三次将较轻的那堆金币中任意一枚和真币进行比较,如果两枚重量相等,说明假币就是那枚未比较的金币;如果两枚重量不等,说明假币就是那枚被比较的金币。 3. 求所有质因子:这个问题可以通过分解质因数的方法来求解。具体做法如下: - 先找出一个小于或等于n的质数p。 - 如果n能被p整除,那么p就是n的因子,把p存入结果集合中,然后将n除以p,得到一个新的数m,继续执行第一步。 - 如果n不能被p整除,那么就找下一个质数p,继续执行第一步。 - 当n=1时,说明所有的质因子都已经找到。 代码实现如下: ```c #include <stdio.h> int main() { int n; printf("请输入一个正整数:"); scanf("%d", &n); printf("%d的所有质因子为:", n); for (int i = 2; i <= n; i++) { while (n % i == 0) { printf("%d ", i); n /= i; } } return 0; } ``` 4. 最大的素数:这个问题可以通过枚举法来求解。具体做法如下: - 从n开始往下枚举每一个数i,如果i是素数,就将它存入结果变量中。 - 一直枚举到1为止。 代码实现如下: ```c #include <stdio.h> #include <stdbool.h> bool is_prime(int n) { if (n < 2) { return false; } for (int i = 2; i * i <= n; i++) { if (n % i == 0) { return false; } } return true; } int main() { int n, max_prime = 0; printf("请输入一个正整数:"); scanf("%d", &n); printf("%d以内最大的素数为:", n); for (int i = n; i >= 2; i--) { if (is_prime(i)) { max_prime = i; break; } } printf("%d", max_prime); return 0; } ``` 5. 字符菱形:这个问题可以通过嵌套循环来实现。具体做法如下: - 外层循环控制行数,内层循环控制每一行的输出。 - 在内层循环中,需要判当前位置是否需要输出星号,如果是,就输出一个星号,否则输出一个空格。 代码实现如下: ```c #include <stdio.h> int main() { int n; printf("请输入菱形的大小:"); scanf("%d", &n); for (int i = 1; i <= n; i++) { for (int j = 1; j <= n - i; j++) { printf(" "); } for (int j = 1; j <= 2 * i - 1; j++) { printf("*"); } printf("\n"); } for (int i = n - 1; i >= 1; i--) { for (int j = 1; j <= n - i; j++) { printf(" "); } for (int j = 1; j <= 2 * i - 1; j++) { printf("*"); } printf("\n"); } return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值