剑指 Offer 49. 丑数
用最小堆或动态规划解决数据的滚动添加维护
题目链接-来源:力扣(LeetCode)
我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。
示例:
输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。说明:
1 是丑数。
n 不超过1690。
思路:
基本思路是,丑数 能且仅能通过已有的丑数继续乘上 2、3、5 质因子生成。于是我们只要在获得一个新的丑数时,将其分别乘上 2、3、5 质因子然后加入后续的集合当中,就能保证不漏过任何一个丑数。添加丑数时,从该集合当中取出 最小值即可。容易想到,用 最小堆实现这个集合可以获得平均每次操作的时间复杂度 O(logn)。
这个方法的瑕疵在于,我们每取出 1 个丑数时,就要新添加 3 个丑数,等于每次额外添加了 2 个数,所以当进行 n 次操作后,我们会浪费 2n 的存储空间。
我们考虑用动态规划来实现。首先明确这样一个前提:每个出现过的丑数,可以拿来乘以 2、3、5 生成 3 个新的丑数,也就是说,这个丑数最多会被使用 3 次,且按 *2、*3、*5 的顺序依次使用各 1 次。
那么我们考虑用一个数组存储丑数, dp[i] 表示当前第 i 个丑数。用 3 个指针 p2、p3、p5 分别从下标为 0 处开始遍历丑数,指针 p2 代表的含义是,「该指针遍历过的数字已经乘过 2 了,而当前指向的数字还没有」, p3 和 p5 同理。那么每次我们需要生成新的丑数 dp[i],就在 p2、p3、p5 对应的丑数中,取一个乘以对应质因子之后结果最小的数,作为新的丑数更新 dp[i]。这样,我们不仅节约了存储空间,还实现了O(n)时间的比较、取数操作。
时间复杂度:O(n)
空间复杂度:O(n)
实现:
1 | class Solution { |