從三月份到如今,大大小小筆試了十幾家公司(主要是由於一直solo code,沒人內推),而後也能感受到本身的進步把。從編程題只能ac一題到後來的ak。今天面騰訊的時候,面試官還一度懷疑我專門去刷了騰訊的筆試題。所以,我想開始作算法,也就是你們都知道的leetcode啦。其實真的蠻有意思的,建議前途未卜的在校大學生均可以去試一下。。算法的確有他獨特的魅力。這個專欄我打算加進一塊是,把我見到的有意思的算法題給拿出來,跟你們分享交流。javascript
input: n output: 一個能夠被1到n的全部整數整除的最小整數, 因爲整數過大,輸出這個整數對987654321取餘的結果
這裏給同窗提個醒。。再往下直接就是我寫得解題思路了,但願你們能夠先本身思考一下這個問題。java
我這裏先給出一個,我跟別人交流以後,感受是能夠繼續發展的想法:node
1
和2
的最小公倍數a1
, 而後求a1
和3
的最小公倍數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
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('\n\n\n') } return result }