leetcode链接:2327. 知道秘密的人数
题目描述
在第 1 天,有一个人发现了一个秘密。
给你一个整数 delay ,表示每个人会在发现秘密后的 delay 天之后,每天 给一个新的人 分享 秘密。同时给你一个整数 forget ,表示每个人在发现秘密 forget 天之后会 忘记 这个秘密。一个人 不能 在忘记秘密那一天及之后的日子里分享秘密。
给你一个整数 n ,请你返回在第 n 天结束时,知道秘密的人数。由于答案可能会很大,请你将结果对 109 + 7 取余 后返回。
示例1:
输入:n = 6, delay = 2, forget = 4
输出:5
解释:
第 1 天:假设第一个人叫 A 。(一个人知道秘密)
第 2 天:A 是唯一一个知道秘密的人。(一个人知道秘密)
第 3 天:A 把秘密分享给 B 。(两个人知道秘密)
第 4 天:A 把秘密分享给一个新的人 C 。(三个人知道秘密)
第 5 天:A 忘记了秘密,B 把秘密分享给一个新的人 D 。(三个人知道秘密)
第 6 天:B 把秘密分享给 E,C 把秘密分享给 F 。(五个人知道秘密)
示例2:
输入:n = 4, delay = 1, forget = 3
输出:6
解释:
第 1 天:第一个知道秘密的人为 A 。(一个人知道秘密)
第 2 天:A 把秘密分享给 B 。(两个人知道秘密)
第 3 天:A 和 B 把秘密分享给 2 个新的人 C 和 D 。(四个人知道秘密)
第 4 天:A 忘记了秘密,B、C、D 分别分享给 3 个新的人。(六个人知道秘密)
限制:
2 <= n <= 1000
1 <= delay < forget <= n
解题思路与代码实现
推导法
定义三个数组:(动态初始化后每个数组每个元素默认为 0 )
① dpKnow[i] 表示第 i 天知道秘密的人数;
② dpForget[i] 表示第 i 天会忘记秘密的人数;
③ dpShare[i] 表示第 i 天可分享秘密的人数。
根据题意,我们得知第 1 天:知道秘密的人数为 1 ,即 A ;将要忘记秘密的人数为 0 ;可以分享秘密的人数为 0 。那么我们可以推导出第 1 + forget 天将要忘记秘密的人数为 1 ;第 1 + delay 天将要可分享秘密的人数为 1 。同时根据题意,第 i 天知道秘密的人数相当于就是第 i - 1 天的人减去第 i 天会忘记秘密的人数再加上第 i 天新知道秘密(分享秘密)的人数;第 i 天新知道秘密(分享秘密)的人数正好就是第 i + forget 天会忘记秘密的人数;第 i + delay 天会分享秘密的人数包括两部分,一部分是第 i 天刚知道秘密的人数,另一部分是第 i + delay - 1 天,知道秘密且会分享的人减去第 i + delay 天会忘记秘密的人数。
以示例一为例:列出每天知道秘密的人
| 第1天 | 第2天 | 第3天 | 第4天 | 第5天 | 第6天 |
|---|---|---|---|---|---|
| A | A | A | A | ||
| B | B | B | B | ||
| C | C | C | |||
| D | D | ||||
| E | |||||
| F |
每天对应的知道秘密的人数、忘记秘密的人数和会分享秘密的人数:
| i | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|
| dpKnow[i] | 1 | 1 | 2 | 3 | 3 | 5 |
| dpForget[i] | 0 | 0 | 0 | 0 | 1 | 0 |
| dpShare[i] | 0 | 0 | 1 | 1 | 1 | 2 |
可见是符合上述规律的,再次总结:当要求某一天知道秘密的人数就要知道那天会忘记秘密的人数和会分享秘密的人数,但是这两者往往之前就已经求出来了或者本来就为0,另外我们是按照时间顺序求知道秘密的人数,所以前一天知道秘密的人数也已经求出来了,那么我们就可以求得那天知道秘密的人数了,同时我们也求出了 forget 天后会忘记秘密的人数以及 delay 天后会分享秘密的人数。
时间复杂度:O(n) 空间复杂度:O(n)
class Solution {
public int peopleAwareOfSecret(int n, int delay, int forget) {
long mod = 1000000007;
long[] dpKnow = new long[n + 1]; // dpKnow[i], 第i天知道秘密的人数
long[] dpForget = new long[n + 1]; // dpForget[i], 第i天会忘记秘密的人数
long[] dpShare = new long[n + 1]; // dpShare[i], 第i天可分享秘密的人数
// 第 1 天:知道秘密的人数为 1 ,即 A ;将要忘记秘密的人数为 0 ;可以分享秘密的人数为 0
dpKnow[1] = 1;
if (1 + forget <= n) {
dpForget[1 + forget] = 1;
}
if (1 + delay <= n) {
dpShare[1 + delay] = 1;
}
// 从第2天开始!i
for (int i = 2; i <= n; i++) {
/*
第 i 天知道秘密的人数为 i - 1 天(前一天)知道秘密的人数
减去第 i 天会忘记秘密的人数再加上第 i 天可分享秘密的人数
*/
dpKnow[i] = (mod + dpKnow[i - 1] - dpForget[i] + dpShare[i]) % mod;
// 第i天,新增知道秘密的,dpShare[i]
if (i + forget <= n) {
/*
dpShare[i] 是第i天,刚知道秘密的人数
这批人,会在i + forget那天忘记秘密
*/
dpForget[i + forget] = dpShare[i];
}
if (i + delay <= n) {
/*
第 i + delay 天会分享秘密的人数包括两大部分:
1、第 i 天刚知道秘密的人 dpShare[i];
2、第 i + delay - 1天,知道秘密且会分享的人减去第 i + delay 天会忘记秘密的人
*/
dpShare[i + delay] = (mod + dpShare[i] + dpShare[i + delay - 1] - dpForget[i + delay]) % mod;
}
}
return (int) dpKnow[n]; //返回第n天知道秘密的人数 dpKnow[n]
}
}
此方法可能不是最优解但比较容易理解。
思考总结
这道题并没有用到复杂的数据结构和算法,写这道题需要对题目给出的条件进行归纳总结,构建结构化数据,找出其隐藏的数学规律。这是Leetcode某一场周赛的题目,最后是看了左神的算法公开课才有了思路,不能只纠结 i 本身,还要关注与其有联系的 i + forget 和 i + delay ,推导出相关数据。思维需要发散开来,誒多多积累吧。