资讯专栏INFORMATION COLUMN

算法之不定期更新(一)(2018-04-12)

Martin91 / 474人阅读

摘要:算法的确有他独特的魅力。然后我在做这个题的时候,其实也用到了类似质因数分解,只是其实我们可以更好的利用到因数这一个特性。判断一个数是否是质数质数列表一开始我们认为每一个数都可能是自身的幂线性筛为质数遍历质数列表为质数的幂

前言

从三月份到现在,大大小小笔试了十几家公司(主要是因为一直solo code,没人内推),然后也能感觉到自己的进步把。从编程题只能ac一题到后来的ak。今天面腾讯的时候,面试官还一度怀疑我专门去刷了腾讯的笔试题。因此,我想开始做算法,也就是大家都知道的leetcode啦。其实真的蛮有意思的,建议前途未卜的在校大学生都可以去试一下。。算法的确有他独特的魅力。这个专栏我打算加进一块是,把我见到的有意思的算法题给拿出来,跟大家分享交流。

题目
input: n
output: 一个可以被1到n的所有整数整除的最小整数,
        由于整数过大,输出这个整数对987654321取余的结果

这里给同学提个醒。。再往下直接就是我写得解题思路了,希望大家可以先自己思考一下这个问题。

解题思路

我这里先给出一个,我跟别人交流之后,感觉是可以继续发展的想法:

先求12的最小公倍数a1, 然后求a13的最小公倍数a2,依次类推最后求出的就是一个可以被所有数整除的最小整数

但是这个方法最大的问题就在于,我们求两个数的最小公倍数的时候,用到的方法非常麻烦,具体大家可以某度质因数分解之类的方法。

然后我在做这个题的时候,其实也用到了类似质因数分解,只是其实我们可以更好的利用到因数这一个特性。

我用一个比较小的例子来说明我的思想吧:
下文题到的因数列表的意思是,恰好能够构成整数的因数的集合

有同学说我因数列表没说明白,那我在这举个例子,12 = 2 * 3 * 2,那么[2, 3, 2]就是他的因数列表

n = 1的时候,这个最大整数要可以被1整除就行,那么这个数就是1,因数列表是[1]

n = 2的时候,这个最大整数要可以被1, 2整除,那么这个数就是1 * 2 = 2,因数列表是[1, 2]

n = 3的时候,这个最大整数要可以被1, 2, 3整除,那么这个数就是1 * 2 * 3 = 6,因数列表是[1, 2, 3]

看到这里其实还看不出什么,但是接下来就是重头戏了

n = 4的时候,这个最大整数要可以被1, 2, 3, 4整除,这时候我们发现,n = 3的时候求出来的6包含了因数[2, 3],而2又恰好是4的因数,那么其实可以发现,这个新的最小整数只要在旧的最小整数6的基础上增添一个新的因数,让4也可以在最小整数的因数列表里面找到所有的因数,那么也就是,我们在原本的因数列表里加入一个新的因数4 / 2 = 2(4 / 2 中的 2 来自 6 的因数列表里的 2),也就是新的因数列表是[1, 2, 3, 2],此时的最小整数是1 * 2 * 3 * 2 = 12

看到这里,我相信大家已经可以明白我的思路了,解决问题的方法就是,求输入为n的最小整数,也就是要在输入为n-1的最小整数的因数列表里面找出n的因数,然后把最后没有找出来的因数给加入到因数列表里面。

而寻找因数的过程可以归结如下:

list // 因数列表, index // 因数列表下标索引,用于循环

k = n / list[index], 如果 k 是个整数,说明list[index]n的一个因数,那么n = k,也就是说,找到了一个因数之后,我们下次要找因数的n就没有那么大了,毕竟已经有一个因数了。

如果k不是一个整数,说明list[index]不是n的因数,不做任何处理

index++

好了,现在我们可以求出输入为n的时候的因数列表了。

一个新的问题来了,计算机没办法表示出这个因数列表的乘积,我们要怎么求出它对987654321的余数呢。答案就是 ((a % c) * (b % c)) % c = (a * b) % c。在这个题里,也就是,我们不断用result乘因数列表里的数的时候,我们就result = result % 987654321,可以在保持结果不变的情况下减少result的值,乘完因数列表里所有的数后的result就是结果

代码

我一个切图仔。。还是js写得舒服一点,用其他语言实现的话,其实理解了我的解题思路应该不难。(by the way)动态数组是真的好用嘻嘻嘻。

 function solution (n) {
     const list = [] // 因数列表
     let result = 1 // 结果
     for (let i = 1; i <= n; i++) { // 依次在不同的n值时的list添加新的因数
         let newFactor = i // 这个新的因数初始为i

          // 在list中寻找i的因数,并且减小newFactor
         for(let j = list.length; j >= 0; j--) {
             if (newFactor === 1) {
                // 此时的i可以被list中若干个数相乘得到
                 break
             }
             let item = list[j]
             if (newFactor % item === 0) { // 如果这个数可以被list[j]整除
                 newFactor /= item // 缩小newFactor
             }
         }
         if (newFactor !== 1) { // 如果这个因数还有剩余部分
             list.push(newFactor) // 把剩余部分添加进list
 
             // 并且把因数乘入result
             result = (result * newFactor) % 987654321
         }
     }
    
    return result
 }

附上测试图把:

那么这个算法题就到这了,如果大家又什么好的想法或者我的有什么问题,欢迎在讨论区和我交流(虽然我知道你们都不想看这又臭又长的)

更好的解法

在评论区里,@JMC_给出了效率更高的解法,本来想补上他的思路的,发现我的文笔有限,说不清楚,大家可以看他的代码JMC_的解法C++版

JMC_的原话是:

刚测试了一下你的代码,发现你这个时间复杂度是O(n*n)。其实算1-n的最小公倍数的话,只要算1-n中的质数的贡献就可以了,每个质数p的贡献就是p的最大幂(小于等于n ),然后将所有的贡献累乘起来就是答案了,这样时间复杂度就会降成O(n)。

我用javascript重写了一下,大家可以用node运行一下,然后自己模仿程序运行的过程应该就可以理解了。

function work (n) {
    const isprime = [] // 判断一个数是否是质数
    const prime = [] // 质数列表
    let result = 1
    for (let i = 2; i <= n; i++) {// 一开始我们认为每一个数都可能是自身的幂
        isprime[i] = i
    }
    for (let i = 2; i <= n; i++) { //线性筛
        if (isprime[i] == i) { //i为质数
            prime.push(i)
            result = (result * i) % 987654321
        }
        for (let j = 0; i * prime[j] <= n; j++) { // 遍历质数列表
            if (isprime[i] === prime[j]) { // i * prime[j]为质数的幂
                isprime[i*prime[j]] = prime[j]
                result = (result * prime[j]) % 987654321
            }
            else isprime[i * prime[j]] = 0
            if (i % prime[j] == 0) break
        }
        console.log(i)
        console.log("isprime")
        console.log(isprime)
        console.log("prime")
        console.log(prime)
        console.log("


")
    }
    return result
}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/94190.html

相关文章

  • 算法定期更新(四)—— 从前序与中序遍历序列构造二叉树(2018-06-02)

    摘要:树的前序,中序遍历的结构如下图可以看出,通过前序遍历可以确定根节点,再通过中序遍历和根节点的位置可以确定出左子树和右子树。另外左子树的前序遍历和中序遍历的顺序跟在其父树中的顺序一样。因此可以确定有一种递归解法。 从前序与中序遍历序列构造二叉树 今天带来的是Leetcode上的一个经典题:从前序与中序遍历序列构造二叉树原题干: /** Definition for a binary ...

    charles_paul 评论0 收藏0
  • 算法定期更新(三)(2018-04-24)

    摘要:实现这个想法之后就发现,时间复杂度真的太高了。本期算法小分享就到这里咯刚做完探索里的初级,还有好多已经说烂了的题就不分享了。如果有什么意见或者想法欢迎在评论区和我交流 题目 input: n // 代表无向图的顶点数 // 从1开始 m // 无向图的边数 arr1 // 各边的情况,形如[[1, 2], [3, 4],...](代表顶点0和顶点2相连,顶点3和顶点4相连) ...

    darryrzhong 评论0 收藏0
  • 算法定期更新(二)(2018-04-15)

    摘要:题目中的数字可以分割出来的连续数字串的所有组合,不同组合之间用一个和连接示例和和和和和和和和和和这里给同学提个醒。。解题思路利用二叉树。说到这这个题就很容易解决了,利用二叉树的层次遍历,每一层遍历的时候都基于上一层的遍历结果生成答案。 题目 input: noutput: 1...n中的数字可以分割出来的连续数字串的所有组合,不同组合之间用一个和连接 示例:input: 3output...

    Aklman 评论0 收藏0
  • moment太重? 那就试试miment--个超轻量级的js时间库

    介绍 Miment 是一个轻量级的时间库(打包压缩后只有1K),没有太多的方法,Miment的设计理念就是让你以几乎为零的成本快速上手,无需一遍一遍的撸文档 由来 首先 致敬一下Moment,非常好用的一个时间库,我本身也是Moment重度使用者,用习惯了Moment,一碰到需要处理时间的需求,立马Moment,不过有时候想想,Moment给我们提供了那么多的功能,但是我们天天用的,也就那么一两个...

    wwolf 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<