前端重拾算法数据结构一个月(6)

144 阅读5分钟

前言

代码总在项目快要完成的时候被推倒重来TAT太疲惫。今天再战!今天听了周杰伦的新专辑嘿嘿,搞得有点晚了。

第六天

延续之前的题数。今天的题目看起来好难,估计得写好一会。

十七题

剑指 Offer 49. 丑数

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

示例:

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

说明:1是丑数。 思路: 仔细读题喔,首先能知道1,2,3,5都是丑数,可以被他们除到1的也是丑数,那么 丑数✖丑数(肯定也)=丑数。那么按照丑数的定义,我先写了一版答案(虽然在写的时候我就已经隐隐约约感觉到了这个时间复杂度非常可怕。)

function nthUglyNumber(n: number): number {
    if(n===1) return 1
    let uglyNum = 1
    const isUgly = (num:number)=>{
        while(num%2===0){
            num/2
        }
        while(num%5===0){
            num/5
        }
        while(num%3===0){
            num/3
        }
        if(num===1) return true
        return false
    }
    for(let i = 0;i<=n;i++){
        uglyNum++
        while(!isUgly(uglyNum)){
            uglyNum
        }
    }
    return uglyNum
};

很好不出所料,果然超时了哈哈哈,做了这么多题第一次遇到超时的。ok那么接着昨天学到的思路,思考一下思考一下,题目给出了三个已知的数字2,3,5。思路转换一下,我上面的思路是去判断一个数是不是丑数,显然判断一个数没有直接创造一个数简单,因此我把方向放到创造丑数上。欸那么在前面的思路中,知道了丑数×丑数=丑数,我们已知1是第一个丑数,那么假设f(1)=1,f(2)=2 = f(1)×2,好像看不太出来,那先继续看f(3)=3=f(1)×3,哦吼有些许眉目。f(4)=4…居然不是f(1)×5,失策了 诶但是是f(2)×2。再看看:

f1=1,f2=2 ,f3= 3,f4= 4 ,f5= 5 ,f6= 6 ,f7= 8,f8= 9,f9=10,f10=12

f1 , f1 *2 , f1 *3 , f2 *2, f1 *5, f2 *3 , f4 *2,f3 *3 ,f2 *5 , f4 *3

看起来还是没有什么规律性,那么应该就是只有通过比较来得到想要的数字。试想,当只有f(1)的时候怎么决定f(2)呢,那就是比较 f(1)×2,f(1)×3,f(1)×5三个数,哪个最小就对啦,但是f(3)如果还是比较这三个数那永远都会是f(1)×2最小,所以说f(2)=f(1)×2之后,f(1)×2这个组合应该要跳过了,×2是肯定要保留的那么改变的就是前面的数f(1)了,所以比较的三个数就变成了f(2)×2,f(1)×3,f(1)×5,诶真的得到的是f(1)×3最小喂。那就可以合理推理接下来比较的三个数就是f(2)×2,f(2)×3,f(1)×5,以此类推…好像就找到规律了喔

function nthUglyNumber(n: number): number {
    let a = 0
    let b = 0
    let c = 0
    const dp = new Array(n)
    dp[0] = 1
    for(let i =1;i<n;i++){
        dp[i] = Math.min(dp[a]*2,dp[b]*3,dp[c]*5)
        //谁是最小的那么它指针往后移
        if(dp[i]===dp[a]*2) a++
        if(dp[i]===dp[b]*3) b++
        if(dp[i]===dp[c]*5) c++
    }
    return dp[n-1]
};

十八题

剑指 Offer 60. n 个骰子的点数

把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。 你需要用一个浮点数数组返回答案,其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i 小的那个的概率。

示例:

输入: 1
输出: [0.16667,0.16667,0.16667,0.16667,0.16667,0.16667]

限制:1 <= n <= 11

思路: 假如掷1个骰子,那就是求[1,6]的概率,2个就是[2,12]的概率,可以知道一个范围来是[n,6n]。那掷n个骰子一共有多少种组合的话就是6^n种了。假设在2个骰子时,骰出点数之和为2的概率就是由能够组成2的点数的组合数(1种,两个1)/总共的组合数(6^n种)。我们知道对于一个骰子来说,1-6的可能性都是相等的,对于n个骰子来说,点数综合的可能性会在综合范围的中间得到最大值,就比如2个骰子,十一个数肯定是在第六个数的时候概率最大,因为达成它的可能性是最多的,而可能性也会由它作为一个中线来对称。从解析里面截一张图出来就可以看出他们的规律了: image.png 在代码中呢,有一个baseRate来代表每一个骰子中的1-6的概率,lastRate是最后要返回的数组,它是要根据骰子数量变大而不断不断变大的,因此我们用let设置它。temp用来存储每加一个骰子的所有点数总和概率,将他不断复制给lastRate。对于for(i)的循环呢,它像是每次加的一个骰子而f(j)循环是表示已经计算到第x位数的概率们。每进一次while就是加了一个骰子。

function dicesProbability(n: number): number[] {
    const baseRate = [1.0/6,1.0/6,1.0/6,1.0/6,1.0/6,1.0/6];
    let lastRate = [1.0/6,1.0/6,1.0/6,1.0/6,1.0/6,1.0/6];
    let temp = [];
    let a = 1;
    while(a<n){
        temp = [];
        for(let i = 0;i<lastRate.length;i++){
            for(let j = 0;j<baseRate.length;j++){
            temp[i+j] = (temp[i+j]==null?0:temp[i+j])+lastRate[i]*baseRate[j];
            }
        }
        lastRate = temp;
        a++;
    }
    return lastRate;
};

ok写了超久,动态规划真的还是有点难以捉摸呢。出来实习差不多一年了,要更新更新简历了,最近事情真是多呢呼。坚持坚持。

代码敲多了一定要起来走走喝水,要温故而知新。

下面是我的文章会引用到的作者和他的文章,都是跟着它学的,感谢。

作者:Krahets 链接:leetcode.cn/leetbook/re… 来源:力扣(LeetCode)