丑数

我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。

示例:
输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。


解题思路
很多题解都讲解了动态规划的基本思路, 但是, 对于为什么要设置 3 个指针 p2, p3, p5 并且进行相应的 +1 操作, 并没有讲的很清楚。

丑数的定义
我们把只包含因子 2、3 和 5 的数称作丑数.

根据题意, 一个丑数必然可以写为 A0 * A1 * … * A(n-1) * AnA0∗A1∗…∗A(n−1)∗An, 其中 A ∈ [2, 3, 5]A∈[2,3,5] 。那么这个丑数也可以写为 (A0 * A1 * … * A(n-1)) * An(A0∗A1∗…∗A(n−1))∗An, (A0 * A1 * … * A(n-1))(A0∗A1∗…∗A(n−1)) 也是个丑数, 而 An ∈ [2, 3, 5]An∈[2,3,5], 所以一个丑数乘以 2, 3, 5 之后, 一定还是一个丑数。

并且如果从丑数序列首个元素开始 *2 *3 *5 来计算, 丑数序列也是不会产生漏解的。

丑数的排列
假设当前存在 3 个数组 nums2, nums3, nums5 分别代表丑数序列从 1 开始分别乘以 2, 3, 5 的序列, 即

nums2 = {12, 22, 32, 42, 52, 62, 82…}
nums3 = {1
3, 23, 33, 43, 53, 63, 83…}
nums5 = {15, 25, 35, 45, 55, 65, 8*5…}

#注意 7 不是丑数.
#2, 3, 5 这前 3 个丑数一定要乘以其它的丑数, 所得的结果才是新的丑数, 所以上例中没有出现 72, 73, 7*5

那么, 最终的丑数序列实际上就是这 3 个有序序列对的合并结果, 计算丑数序列也就是相当于 合并 3 个有序序列。

合并 3 个有序序列
合并 3 个有序序列, 最简单的方法就是每一个序列都各自维护一个指针, 然后比较指针指向的元素的值, 将最小的放入最终的合并数组中, 并将相应指针向后移动一个元素。 这也就是:

class Solution {
public:
	int nthUglyNumber(int n) {
		// ....
		dp[i] = min(min(dp[p2] * 2, dp[p3] * 3), dp[p5] * 5);
		if (dp[i] == dp[p2] * 2) 
			p2++;
		if (dp[i] == dp[p3] * 3) 
			p3++;
		if (dp[i] == dp[p5] * 5) 
			p5++;
                  // ......
};

这段代码所做的事情。

合并过程中重复解的处理
nums2, nums3, nums5 中是存在重复的解的, 例如 nums2[2] == 3 * 2, nums3[1] == 2 * 3 都计算出了 6 这个结果, 所以在合并 3 个有序数组的过程中, 还需要跳过相同的结果, 这也就是为什么在比较的时候, 需要使用 3 个并列的 if… if… if… 而不是 if… else if… else 这种结构的原因。 当比较到元素 6 时, if (dp[i] == dp[p2] * 2)…if (dp[i] == dp[p3] * 3)… 可以同时指向 nums2, nums3 中 元素 6 的下一个元素。

class Solution {
    public int nthUglyNumber(int n) {
        int[] dp = new int[n];
        dp[0] = 1;
        int p2 = 0, p3 = 0, p5 = 0;
        for(int i = 1; i < n; i++){
            dp[i] = Math.min(dp[p2] * 2, Math.min(dp[p3] * 3, dp[p5] * 5));
            if(dp[i] == dp[p2] * 2)
                p2++;
            if(dp[i] == dp[p3] * 3)
                p3++;
            if(dp[i] == dp[p5] * 5)
                p5++;
        }
        return dp[n - 1];
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值