丑数II [问题本质 & 优先队列 & 动规]

160 阅读2分钟

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第4篇文章,丑数II [问题本质 & 优先队列 & 动规] - 掘金 (juejin.cn)

前言

抓住问题本质,是解题的关键,也是举一反三的关键。丑数的本质是2x/3x/5x,可采用优先队列选择最小x,也可以动规取得当前最小丑数作为当前状态。

一、丑数II

image.png

二、优先队列 & 动规

1、优先队列

// 丑数II
public class NthUglyNumber {
    // core:抓住问题本质,1 2x 3x 5x 每次取最小值为x,将2x/3x/5x加入堆中,set去重,取到第n个最小数即可。
    public int nthUglyNumber(int n) {
        PriorityQueue<Long> que = new PriorityQueue<>();

        que.add(1l);

        return getNth(que, n);
    }

    public int getNth(PriorityQueue<Long> que, int n) {
        int r = 1;
        Set<Long> pool = new HashSet<>();
        while (n-- != 0) {
            r = que.poll().intValue();
            long v1 = r * 2l, v2 = r * 3l, v3 = r * 5l;
            if (!pool.contains(v1)) {
                que.add(v1);
                pool.add(v1);
            }
            if (!pool.contains(v2)) {
                que.add(v2);
                pool.add(v2);
            }
            if (!pool.contains(v3)) {
                que.add(v3);
                pool.add(v3);
            }
        }
        return r;
    }
}

2、动态规划

// 抓住问题本质,进行动态规划。
class NthUglyNumber2 {
    // 1 2x 3x 5x 每次取其结果的最小值作为下一个状态,并更新x++
    public int nthUglyNumber(int n) {
        // 状态定义
        long[] f = new long[n + 1]; // f[i]表示第i个丑数。
        // 初始化
        f[1] = 1;
        // 三个指针,分别指着2x/3x/5x的变量x.
        int i = 1, j = 1, k = 1;
        // 状态转移。
        for (int idx = 2; idx <= n; idx++) {
            long v1 = 2l * f[i], v2 = 3l * f[j], v3 = 5l * f[k];
            // 取最小。
            long min = Math.min(v1, Math.min(v2, v3));
            // 记录下一个最小丑数。
            f[idx] = min;
            // 看3个变量x设满足条件,谁++。
            if (v1 == min) ++i;
            if (v2 == min) ++j;
            if (v3 == min) ++k;
        }
        // 最终状态。
        return (int) f[n];
    }

}

总结

1)抓住问题本质是关键,选择不同的数据结构是举一反三,丑数的本质为2x/3x/5x为丑数。

2)优先队列,快速获取最小/最大元素;Set的不重复性可以去重。

3)动态规划,找到状态,将大问题转换为规模更小性质相同的子问题,寻找状态转移。

4)int经常溢出,可long替代,而平时的普通数字都是int,如果不带l,那么乘除来的结果就是int,只是最终赋值给了long型。而Long这种包装型就有严格的类型,不会隐式转换,直接编译不通过,Double与Float同理。

参考文献

[1] LeetCode 丑数II