题目描述
思路解析动画文字版
记住两句:新学会 dp[i] = 窗口 [i-forget+1, i-delay] 的新增之和;最终答案 = 最近 forget 天新学会的人数之和。下面每帧都在套它。
开局:第 1 天有 1 个人知道秘密,所以 dp[1]=1(绿色)。下标 0 是占位守卫(灰),恒为 0。从第 2 天起,每天的新增都要靠前面几天推出来。
推 第 2 天 的新增 dp[2]。窗口 [-1, 0] 整个落在第 1 天之前,还没有人到了能分享的日子,所以今天没有新学会的人。
把结果填进去:dp[2]=0(绿色)。这一格从此定死,作为后面更晚那些天的贡献来源。
推 第 3 天 的新增 dp[3]。能在今天教新人的,是学会日落在第 1 到 1 天(黄框)的人:更早的已经忘、更晚的还没到分享日。
把黄框里这几天的新增加起来:dp[1]=1,一共 1 人。也就是说 第 3 天 会新学会 1 个人。
把结果填进去:dp[3]=1(绿色)。这一格从此定死,作为后面更晚那些天的贡献来源。
推 第 4 天 的新增 dp[4]。能在今天教新人的,是学会日落在第 1 到 2 天(黄框)的人:更早的已经忘、更晚的还没到分享日。
把黄框里这几天的新增加起来:dp[1]=1,dp[2]=0,一共 1 人。也就是说 第 4 天 会新学会 1 个人。
把结果填进去:dp[4]=1(绿色)。这一格从此定死,作为后面更晚那些天的贡献来源。
推 第 5 天 的新增 dp[5]。能在今天教新人的,是学会日落在第 2 到 3 天(黄框)的人:更早的已经忘、更晚的还没到分享日。
把黄框里这几天的新增加起来:dp[2]=0,dp[3]=1,一共 1 人。也就是说 第 5 天 会新学会 1 个人。
把结果填进去:dp[5]=1(绿色)。这一格从此定死,作为后面更晚那些天的贡献来源。
推 第 6 天 的新增 dp[6]。能在今天教新人的,是学会日落在第 3 到 4 天(黄框)的人:更早的已经忘、更晚的还没到分享日。
把黄框里这几天的新增加起来:dp[3]=1,dp[4]=1,一共 2 人。也就是说 第 6 天 会新学会 2 个人。
把结果填进去:dp[6]=2(绿色)。这一格从此定死,作为后面更晚那些天的贡献来源。
到这里,1 到 6 天每天新学会多少人都推完了。但注意 dp 是「当天新学会」的人数,不是总人数。第 6 天还知道秘密的,只有最近 forget=4 天里学会、还没忘的人。
第 6 天结束时,只有第 3 到 6 天(黄框)学会的人还没忘、仍知道秘密。更早学会的(第 1、2 天)满 forget 天已经忘了。把黄框里的 dp 加起来即答案。
加上第 3 天新学会的 dp[3]=1 人,running 小计到 1。绿色是已经数进答案的天。
加上第 4 天新学会的 dp[4]=1 人,running 小计到 2。绿色是已经数进答案的天。
加上第 5 天新学会的 dp[5]=1 人,running 小计到 3。绿色是已经数进答案的天。
加上第 6 天新学会的 dp[6]=2 人,running 小计到 5。绿色是已经数进答案的天。
最近 4 天学会的人加满,一共 5 人,这就是第 6 天知道秘密的总人数,答案 5。和题面示例 1 的 5 完全对上。
三组边界都可以自己按「窗口求和 + 最近 forget 天累加」手推验证。
两个高频追问:区间端点的开闭理由,以及差分数组的统计优势。
参考代码
from __future__ import annotationsfrom typing import *from collections import *from functools import *from itertools import *from math import *from heapq import *from bisect import *class TreeNode: def __init__(self, val=0, left=None, right=None): self.val = val self.left = left self.right = rightclass ListNode: def __init__(self, val=0, next=None): self.val = val self.next = nextclass Solution: def peopleAwareOfSecret(self, n: int, delay: int, forget: int) -> int: m = (n << 1) + 10 d = [0] * m cnt = [0] * m cnt[1] = 1 for i in range(1, n + 1): if cnt[i]: d[i] += cnt[i] d[i + forget] -= cnt[i] nxt = i + delay while nxt < i + forget: cnt[nxt] += cnt[i] nxt += 1 mod = 10**9 + 7 return sum(d[: n + 1]) % mod复杂度
- 时间:O(n·(forget−delay)),每天要把窗口内 forget−delay 个数相加;用滑动窗口维护跑动和可降到 O(n)
- 空间:O(n),开长度约 2n 的 dp/差分数组
易错点
面试追问把动画讲成自己的话
追问分享区间为什么是左闭右开 [i+delay, i+forget)?
追问参考代码为什么用差分数组统计答案,而不是每天遍历一遍?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
统计构造好字符串的方案数
LeetCode 2466 · 中等 · 沿着 动态规划套路 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题