[前端]_一起刷leetcode 313. 超级丑数

175 阅读5分钟

大家好,我是挨打的阿木木,爱好算法的前端摸鱼老。最近会频繁给大家分享我刷算法题过程中的思路和心得。如果你也是想提高逼格的摸鱼老,欢迎关注我,一起学习。

题目

313. 超级丑数

超级丑数 是一个正整数,并满足其所有质因数都出现在质数数组 primes 中。

给你一个整数 n 和一个整数数组 primes ,返回第 n 个 超级丑数 。

题目数据保证第 n 个 超级丑数 在 32-bit 带符号整数范围内。

 

示例 1:

输入: n = 12, primes = [2,7,13,19]
输出: 32 
解释: 给定长度为 4 的质数数组 primes = [2,7,13,19],前 12 个超级丑数序列为:[1,2,4,7,8,13,14,16,19,26,28,32] 。

示例 2:

输入: n = 1, primes = [2,3,5]
输出: 1
解释: 1 不含质因数,因此它的所有质因数都在质数数组 primes = [2,3,5] 中。

 

提示:

  • 1 <= n <= 106
  • 1 <= primes.length <= 100
  • 2 <= primes[i] <= 1000
  • 题目数据 保证 primes[i] 是一个质数
  • primes 中的所有值都 互不相同 ,且按 递增顺序 排列

思路

这道题目是我们上一道题目丑数的进阶,如果还不知道怎么刷这种题型的题目的话可以先看我上一篇文章。[路飞]_一起刷leetcode 264. 丑数 II

只不过这道题目是把上到题目的 [2, 3, 5] 改成了现在指定的数组primes,不过刷题的思路是一样的,那么我们就不能直接指定三根指针了,我们可以动态创建一个等于primes.length的数组来指定每个位置的指针所在位置。由于题目中已经说了: primes 中的所有值都 互不相同 ,且按 递增顺序 排列,那么我们还可以在代码中做一个优化,即遇到第一个指针为0的就不用往下执行了,因为递增序列中再往后也找不到比当前值更小的了。

实现

/**
 * @param {number} n
 * @param {number[]} primes
 * @return {number}
 */
var nthSuperUglyNumber = function(n, primes) {
    // 老规矩,第一个元素从1开始
    let result = [ 1 ];

    // 创建一个跟数组一样长度的指针数组, 一一对应
    const len = primes.length;
    let dp = new Array(len).fill(0);

    while (result.length < n) {
        // 拿最大值
        let minValue = Number.MAX_VALUE;
        let maxIndex = len;

        // 遇到第一个为0的指针直接终止
        for (let i = 0; i < len; i++) {
            minValue = Math.min(minValue, primes[i] * result[dp[i]]);

            if (dp[i] === 0) {
                maxIndex = i;
                break;
            }
        }

        // 这里也一样,到位置直接结束
        for (let i = 0; i <= maxIndex; i++) {
            if (primes[i] * result[dp[i]] === minValue) {
                dp[i]++;
            }
        }

        result.push(minValue);
    }

    return result[n - 1];
};

结果

image.png

最后发现通用解法并不快,于是咱们继续做优化。还是那个问题,我们每一轮中的计算都得重复执行两遍,那么我们如何缓存每一个值的计算结果呢?这里可以用Map也可以用数组,为了方便这道题目我们就直接用数组吧,创建一个同样长度的数组,如果有计算值我们就进行缓存,如果没有的话就默认值用1填充。为什么用1不用0呢? 这就是加法和乘法基数的区别了。加法都是从0开始加,乘法都是从1开始乘。

优化代码

/**
 * @param {number} n
 * @param {number[]} primes
 * @return {number}
 */
var nthSuperUglyNumber = function(n, primes) {
    // 老规矩,第一个元素从1开始
    let result = [ 1 ];

    // 创建一个跟数组一样长度的指针数组, 一一对应
    const len = primes.length;
    let dp = new Array(len).fill(0);
    // 创建一个存储每个节点的值的数组
    let nums = [];

    while (result.length < n) {
        // 拿最大值
        let minValue = Number.MAX_VALUE;
        let maxIndex = len;

        // 遇到第一个为0的指针直接终止
        for (let i = 0; i < len; i++) {
            nums[i] = nums[i] || primes[i] * result[dp[i]];
            minValue = Math.min(minValue, nums[i]);

            if (dp[i] === 0) {
                maxIndex = i;
                break;
            }
        }

        // 这里也一样,到位置直接结束
        for (let i = 0; i <= maxIndex; i++) {
            if (nums[i] === minValue) {
                dp[i]++;
                // 值往后走一步
                nums[i] = primes[i] * result[dp[i]]
            }
        }

        result.push(minValue);
    }

    return result[n - 1];
};

优化到了这一步,发现效果还是不明显,于是我又做了一轮优化,一开始就把结果数组和记录每一步的值数组都创建好,然后直接进行赋值操作不重新去push元素了。而且把while循环改成for循环,这样子一来速度提升了一大半。

最终代码

/**
 * @param {number} n
 * @param {number[]} primes
 * @return {number}
 */
var nthSuperUglyNumber = function(n, primes) {
    const len = primes.length;

    // 老规矩,第一个元素从1开始, 直接创建好整个数组
    let result = new Array(n).fill(1);;
    // 创建一个跟数组一样长度的指针数组, 一一对应
    
    let dp = new Array(len).fill(0);
    // 创建一个存储每个节点的值的数组
    let nums = new Array(len).fill(0);

    for (let j = 1; j < n; j++) {
        // 拿最大值
        let minValue = Number.MAX_VALUE;

        // 遇到第一个为0的指针直接终止
        for (let i = 0; i < len; i++) {
            nums[i] = nums[i] || primes[i] * result[dp[i]];
            minValue = Math.min(minValue, nums[i]);
        }

        result[j] = minValue;

        // 这里也一样,到位置直接结束
        for (let i = 0; i <= len; i++) {
            if (nums[i] === minValue) {
                dp[i]++;
                // 值往后走一步
                nums[i] = primes[i] * result[dp[i]]
            }
        }
    }

    return result[n - 1];
};

最终结果

image.png

看懂了的小伙伴可以点个关注、咱们下道题目见。如无意外以后文章都会以这种形式,有好的建议欢迎评论区留言。