从零学算法2327
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
-
定义数组know[i]表示恰好在第i天知道秘密的人
-
那么当 j 在[i+delay,i+forget-1]区间时,第j天的人都会因为之前的人恰好在第i天知道秘密,因此让第j天的人也恰好在这个区间中某一天被分享而知道了秘密
-
而统计最后结果时,我们只要统计在到第n天时还在遗忘期限内的人即可,即n<=i+forget-1
-
public int peopleAwareOfSecret(int n, int delay, int forget) {final int MOD = (int)1e9 + 7;int[] know = new int[n + 1];know[1] = 1;long ans = 0;for(int i = 1; i <= n; i++){// 这天以及之后的人来不及遗忘秘密// 而这之前的人都会因为经过了forget天还没到n天而遗忘秘密,所以不计入ansif(i + forget - 1 >= n){ans += know[i];}for(int j = i + delay; j <= Math.min(i + forget - 1, n); j++){know[j] = (know[j] + know[i]) % MOD;}}return (int)(ans % MOD);}
-
know[i+delay,i+forget-1]
都加上know[i]
,可以用差分数组优化 -
know[1] = 1 -> d[1] = 1
,know[2] = 0 -> d[2] = 0 - 1 = -1
-
public int peopleAwareOfSecret(int n, int delay, int forget) {final int MOD = (int)1e9 + 7;int[] d = new int[n + 1];// know[] = [0,1,0,0,...]// 对应d[] = [0,1,-1,0,...]d[1] = 1;d[2] = -1;int know = 0;long ans = 0;for(int i = 1; i <= n; i++){// know 相当于之前的 know[i],因为d[0]+...+d[i] = know[i]know = (know + d[i]) % MOD;if(i + forget - 1 >= n){ans += know;}// know[i+delay,i+forget-1]的人都增加know[i]// 对应d[i+delay] += know, d[i+forget] -= knowif(i + delay <= n){d[i + delay] = (d[i + delay] + know) % MOD;}if(i + forget <= n){d[i + forget] = (d[i + forget] - know + MOD) % MOD;}}return (int)(ans % MOD);}
-
前缀和:设 sum[i] = 第i天结束时,累计知道过秘密的人数
- 我们要的结果是在第 n - forget + 1 到第 n 天新增的知道秘密的人数,这些人还没来得及遗忘,是到第 n 天还记得秘密的人
- 第i到j天知道的人数为sum[j] - sum[i-1]
- 所以最终结果为 sum[n] - sum[n - forget]
-
从第一天开始不断累加今天新增的知道秘密的人数know,就能得到最终的 sum
- 即不断 sum[j] = sum[j - 1] + know
-
最终难点就是计算出 know,在第 j 天新增知道的秘密的人数其实就是在第 j 天能够分享秘密的人数总和
- 假设在第 k 天知道秘密,第 j 天能分享,首先要经过 delay 天
- 即
j >= k + delay
->k <= j - delay
- 即
- 其次不能经过 forget 天否则会忘记
- 即
j <= k + forget - 1
->k >= j - forget + 1
- 即
- 也就是说在第
j-forget+1 ~ j - delay
天之间知道秘密的总人数就是 know,你会发现这不就是sum[j - delay] - sum[j-forget]
- 假设在第 k 天知道秘密,第 j 天能分享,首先要经过 delay 天
-
public int peopleAwareOfSecret(int n, int delay, int forget) {final int MOD = (int)1e9 + 7;int[] sum = new int[n + 1]; // known 数组的前缀和sum[1] = 1;for(int j = 2; j <= n; j++){// 第 j 天新增的知道秘密的人数int know = (sum[Math.max(j-delay,0)] - sum[Math.max(j-forget,0)]) % MOD;// 前几天知道的总人数 + 新增的等于到第j天为止知道的总和sum[j] = (sum[j-1] + know) % MOD;}int ans = sum[n] - sum[Math.max(n-forget, 0)];return (int)(ans % MOD + MOD)%MOD; // 保证答案非负}