算法思想一:优先队列
解题思路:
采用一个简单的解法是使用优先队列:
1、起始先将最小丑数 1 放入队列
2、每次从队列取出最小值 x,然后将 x 所对应的丑数 2x、3x 和 5x 进行入队。
3、对步骤 2 循环多次,第 n 次出队的值即是答案。
注:为了防止同一丑数多次进队,需要使用数据结构 Set(explored) 来记录入过队列的丑数
代码展示:
Python版本
# -*- coding:utf-8 -*- import heapq class Solution: def GetUglyNumber_Solution(self, index): # write code here # 丑数质因子 nums = [2,3,5] # set 列表 explored = {1} pq = [1] for i in range(1, index+1): x = heapq.heappop(pq) if i == index: return x # 分别将x与质因子计算 并添加到 explored 中 for num in nums: t = num * x # 用 explored 记录进入队列的丑数 if t not in explored: explored.add(t) heapq.heappush(pq,t) return 0
复杂度分析
时间复杂度:从优先队列中取最小值为 O(1),往优先队列中添加元素复杂度为 ,算法需要循环n次。整体复杂度为 。
空间复杂的:队列和set列表的存储空间
算法思想二:动态规划
解题思路:
方法一使用优先队列,会预先存储较多的丑数计算复杂,导致复杂度较高,可以使用动态规划的方法进行优化。
1、定义数组 dp,其中 dp[i] 表示第 i 个丑数,第 n 个丑数即为 dp[n]。由于最小的丑数是 1,因此 dp[1]=1。 特殊情况, n = 0, 直接返回0
2、定义三个指针 p2,p3,p5,表示下一个丑数是当前指针指向的丑数乘以对应的质因数。初始时,三个指针的值都是 1。
3、当 2≤i≤n 时,令 ,然后分别比较 和 ,, 是否相等,如果相等则将对应的指针加 1。
4、循环结束返回
图解:
代码展示:
JAVA版本
public class Solution { public int GetUglyNumber_Solution(int index) { // 特殊情况 if (index <= 0){ return 0; } // dp数组并初始化 int[] dp = new int[index + 1]; dp[1] = 1; // 三指针 int p2 = 1, p3 = 1, p5 = 1; for (int i = 2; i <= index; i++) { // 分别计算三指针与质因子的乘积,选择最小值 int num2 = dp[p2] * 2, num3 = dp[p3] * 3, num5 = dp[p5] * 5; dp[i] = Math.min(Math.min(num2, num3), num5); // 判断是哪个指针与质因子的乘积,指标前进 if (dp[i] == num2) { p2++; } if (dp[i] == num3) { p3++; } if (dp[i] == num5) { p5++; } } // 返回dp数组最后一个元素 return dp[index]; } }
复杂度分析
时间复杂度:。需要计算数组dp 中的 n 个元素,每个元素的计算都可以在 O(1) 的时间内完成。
空间复杂度:。空间复杂度主要取决于数组 dp 的大小。