劍指offer——醜數
我們把只包含質因子 2、3 和 5 的數稱作醜數(Ugly Number)。求按從小到大的順序的第 n 個醜數。 示例: 輸入: n = 10 輸出: 12 解釋: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 個醜數。 說明: 1是醜數。 n不超過1690。 來源:力扣(LeetCode) 連結:https://leetcode-cn.com/problems/chou-shu-lcof
題目對醜數的概念很簡短,可能一時間看不明白。
因子:現在有n*m=p,則n和m是p因子。比如2*6=12,那麼2和6就是12的因子。
質數:一個數的因子只有1和這個數本身,那麼這個數就是質數。比如2的因子只有1和2;5的因子只有1和5;所以2,5是質數。
醜數:有質因子2,3,5,醜數=小丑數*(2或3或5),醜數的因子分解出來可以全是2,3,5。比如12=2*2*3,10=2*5,9=3*3*3。公式為:2x
解題思路:
由上面的說法可以想出一種簡單方法來判斷一個數number是否是醜數:
將number迴圈除以2,直到不能整除。
再將得到的商分別迴圈除以3,5,如果最後能夠整除,且商為1,那麼這個數是醜數。
例如:
12/2=6 6/2=3 3/2=1...1(有餘數1,換3) 3/3=1 (符合條件,為醜數)
14/2=7 7/2=3...1 (有餘數,換3) 7/3=2...1 (有餘數,換5) 7/5=1...2 (不符合條件,不是醜數)
判斷醜數程式碼如下:
var isUgly=function(num){ while(num%2==0) num=num/2; while(num%3==0) num=num/3; while(num%5==0) num=num/5; return ( num===1)?true:false; }
要找出第n個醜數,那麼可以選擇遍歷
解1
var isUgly=function(num){ while(num%2==0) num=num/2; while(num%3==0) num=num/3; while(num%5==0) num=num/5; return ( num===1)?true:false; } var nthUglyNumber = function(n) { if(n<=0) return 0; var index=0; var current=0; while(index<n){ current++; if(isUgly(current)){ index++; } } return current; };
這樣的方法理解起來比較容易,但是明顯的缺點就是時間消耗比較大,每個數都要判斷是不是醜數,然後我們要找第1600個醜數呢?那麼我們將會判斷上億個數字是否為醜數。
下面有一種是靠空間換時間的方法,是劍指offer書上提供的方法。具體解析參考書上的解釋。
解題思路:
如果我們將已經找到的醜數按從小到大的方式有序的存入陣列uglyArr。現在知道的最大丑數為M,我們需要找到下一個醜數。下一個醜數MNEXT應該是當前uglyArr陣列中的某個醜數的2倍,3倍或者5倍。我們可以將uglyArr陣列中的數都變成2倍,3倍或5倍。他們都是醜數,但是我們要找的Mnext是大於M的醜數中最小的那一個。比如現在uglyArr=[1,2,3,4,5],M=5,我們現在找Mnext。
2倍:uglyArr*2=[2,4,6,8,10]
3倍:uglyArr*3=[3,6,9,12,15]
5倍:uglyArr=[5,10,15,20,25]
比5大的數:6,8,10,12,15,20,25。Mnext是大於M的醜數中最小的那一個,Mnext=6。這樣就找到了下一個醜數。
我們還可以繼續優化,因為uglyArr陣列中有些數是可以在2,3,5倍時只比M大一點點,我們只需要找出這個臨界的數即可,設為t2,t3,t5。就如上面例子中,t2=3,t3=2,t5=2,t2*2=6, t3*3=6,t5*5=10。6,6,10相比,最小為6。
解2
var minNum=function(a,b,c){ return a<b?(a<c?a:c):(b<c?b:c) } var nthUglyNumber = function(n) { //如果小於等於0,那麼可以直接返回0 if(n<=0) return 0; var uglyArr=[1];//初始化醜數陣列,這將是一個有序的醜數陣列 var currentIndex=1;//當前要尋找的醜數在陣列中的索引位置 //t2,t3,t5代表*2,*3,*5剛好大於或等於當前醜數陣列中最大的醜數,最開始初始化為1 var t2=1; var t3=1; var t5=1; while(currentIndex<n){ var min=minNum(t2*2,t3*3,t5*5);//下一個醜數是t2*2,t3*3,t5*5中最小的 uglyArr.push(min); //尋找臨界值 var temp=0; while(uglyArr[temp]*2<=min) temp++; t2=uglyArr[temp]; temp=0; while(uglyArr[temp]*3<=min) temp++; t3=uglyArr[temp]; temp=0; while(uglyArr[temp]*5<=min) temp++; t5=uglyArr[temp]; currentIndex++; } return uglyArr[currentIndex-1]; };
也可以用動態規劃的方法來實現,參考https://leetcode-cn.com/problems/chou-shu-lcof/solution/mian-shi-ti-49-chou-shu-dong-tai-gui-hua-qing-xi-t/,這裡寫的解釋很詳細,圖文並茂,我這裡用javascript來實現
解3
var minNum=function(a,b,c){ return a<b?(a<c?a:c):(b<c?b:c) } var nthUglyNumber = function(n) { if(n<=0) return 0; var a=0, b=0, c=0;//三個位置 var dp=[1] for(var i=0;i<n;i++){ var min=minNum(dp[a]*2,dp[b]*3,dp[c]*5); dp.push(min) while(dp[a]*2<=min) a++; while(dp[b]*3<=min) b++; while(dp[c]*5<=min) c++; } return dp[n-1]; };