指尖划过的轨迹,藏着最细腻的答案~
题目:
在第 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
分析一:
对于一个知道秘密的人i来说其会在[i + delay, i + forget - 1]的每一天中分享秘密给另一个新的人,题目要求最终输出知道秘密的人数。
我们可以维护一个数组knowns[i],表示在第i天新增的知道秘密的人数,其可以在j = i + delay, i + delay + 1, ...,i + forget - 1中的每一天分享秘密给一个新的人,即knowns[j]加上knowns[i]。
初始化knowns[1] = 1表示第一知道秘密的人。
答案为第n天没有忘记秘密的人,需要满足,i + forget - 1 > n,即i > n - forget + 1,所以答案我knowns中的[max(n - forget + 1, 1), n]的元素和。
class Solution {
const int MOD = 1'000'000'007;
public:
int peopleAwareOfSecret(int n, int delay, int forget) {
vector<int> knowns(n + 1);
knowns[1] = 1;
long long ans = 0;
for (int i = 1; i <= n; i++) {
if (i >= n - forget + 1) {
ans += knowns[i];
}
for (int j = i + delay; j <= min(i + forget - 1, n); j++) {
knowns[j] = (knowns[i] + knowns[j]) % MOD;
}
}
return ans % MOD;
}
};
复杂度分析
- 时间复杂度:O(n(forget−delay))。
- 空间复杂度:O(n)。
差分数组优化
差分数组定义与性质:
对于数组a,定义其差分数组d[i]为:
其有如下两条性质:
- 从左到右累加差分数组可还原为数组a
- 对数组a从i到j累加num等价于将差分数组的
d[i] + num,将d[j + 1] - num
使用性质二,我们可以在O(1)的时间对a的子数组进行操作,然后使用性质1还原数组a。
本题优化:
对于本题来说,j = [i + delay, i + forget - 1]都会增加一个knowns[i],这可以使用差分数组优化。
具体的,对于初始的knowns[1] = 1,翻译为差分数组为:diff[1] = 1; diff[2] = -1;
class Solution {
const int MOD = 1'000'000'007;
public:
int peopleAwareOfSecret(int n, int delay, int forget) {
vector<int> diff(n + 1);
diff[1] = 1;
diff[2] = -1;
long long ans = 0;
int known = 0;
for (int i = 1; i <= n; i++) {
known = (known + diff[i]) % MOD;
if (i >= n - forget + 1) {
ans += known;
}
if (i + delay <= n) { // 差分数组开始增加num
diff[i + delay] = (diff[i + delay] + known) % MOD;
}
if (i + forget <= n) { // 差分数组结束减少num
diff[i + forget] = (diff[i + forget] - known + MOD) % MOD;
}
}
return ans % MOD;
}
};
前缀和优化:
对于knowns[j]来说其会被那些knows[i]更新呢? 解得:
那么计算[j - forget + 1, j - delay]的子数组和,即可得到knowns[j]的值,而子数组和可以使用前缀和。
class Solution {
const int MOD = 1'000'000'007;
public:
int peopleAwareOfSecret(int n, int delay, int forget) {
vector<int> sum(n + 1);
sum[1] = 1;
for (int j = 2; j <= n; j++) {
int known = (sum[max(j - delay, 0)] - sum[max(j - forget, 0)]) % MOD;
sum[j] = (sum[j - 1] + known) % MOD;
}
int ans = sum[n] - sum[max(n - forget, 0)];
return (ans % MOD + MOD) % MOD; // 保证结果非负
}
};