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

前言

從三月份到如今,大大小小筆試了十幾家公司(主要是由於一直solo code,沒人內推),而後也能感受到本身的進步把。從編程題只能ac一題到後來的ak。今天面騰訊的時候,面試官還一度懷疑我專門去刷了騰訊的筆試題。所以,我想開始作算法,也就是你們都知道的leetcode啦。其實真的蠻有意思的,建議前途未卜的在校大學生均可以去試一下。。算法的確有他獨特的魅力。這個專欄我打算加進一塊是,把我見到的有意思的算法題給拿出來,跟你們分享交流。javascript

題目

input: n
output: 一個能夠被1到n的全部整數整除的最小整數,
        因爲整數過大,輸出這個整數對987654321取餘的結果

這裏給同窗提個醒。。再往下直接就是我寫得解題思路了,但願你們能夠先本身思考一下這個問題。java



















解題思路

我這裏先給出一個,我跟別人交流以後,感受是能夠繼續發展的想法:node

  • 先求12的最小公倍數a1, 而後求a13的最小公倍數a2,依次類推最後求出的就是一個能夠被全部數整除的最小整數

可是這個方法最大的問題就在於,咱們求兩個數的最小公倍數的時候,用到的方法很是麻煩,具體你們能夠某度質因數分解之類的方法。面試

而後我在作這個題的時候,其實也用到了相似質因數分解,只是其實咱們能夠更好的利用到因數這一個特性。算法

我用一個比較小的例子來講明個人思想吧:
下文題到的因數列表的意思是,剛好可以構成整數的因數的集合編程

有同窗說我因數列表沒說明白,那我在這舉個例子,12 = 2 * 3 * 2,那麼[2, 3, 2]就是他的因數列表ubuntu

  • 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的因數,而後把最後沒有找出來的因數給加入到因數列表裏面。測試

而尋找因數的過程能夠歸結以下:spa

  1. list // 因數列表, index // 因數列表下標索引,用於循環
  2. k = n / list[index], 若是 k 是個整數,說明list[index]n的一個因數,那麼n = k,也就是說,找到了一個因數以後,咱們下次要找因數的n就沒有那麼大了,畢竟已經有一個因數了。
  3. 若是k不是一個整數,說明list[index]不是n的因數,不作任何處理
  4. 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('\n\n\n')
    }
    return result
}
相關文章
相關標籤/搜索