劍指 Offer 49. 醜數(三指標)
- 題目描述
我們把只包含質因子 2、3 和 5 的數稱作醜數(Ugly Number)。求按從小到大的順序的第 n 個醜數。 示例: 輸入: n = 10 輸出: 12 解釋: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 個醜數。 說明: 1是醜數。 n不超過1690。
- 三指標解法
這道題很容易想到的是,醜叔的集合是dp[i]=dp[a] *2+dp[b]*3+dp[c]*5,即醜叔一定是2 3 5 的倍數,但如何保證從小到大生成這些有序的醜數呢,那我們其實很容易想到三指標,這三個指標分別指向2的乘數,3的乘數和5的乘數。
我們知道,醜數的排列肯定是1,2,3,4,5,6,8,10.... 然後有一個特點是,任意一個醜數都是由小於它的某一個醜數*2,*3或者*5得到的,那麼如何得到所有醜數呢? 現在假設有3個數組,分別是: A:{1*2,2*2,3*2,4*2,5*2,6*2,8*2,10*2......}
B:{1*3,2*3,3*3,4*3,5*3,6*3,8*3,10*3......}
C:{1*5,2*5,3*5,4*5,5*5,6*5,8*5,10*5......}
那麼所有醜數的排列,必定就是上面ABC3個數組的合併結果然後去重得到的,那麼這不就轉換成了三個有序陣列的無重複元素合併的問題了嗎?而這三個陣列就剛好是{1,2,3,4,5,6,8,10....}乘以2,3,5得到的。
合併有序陣列的一個比較好的方法,就是每個陣列都對應一個指標,然後比較這些指標所指的數中哪個最小,就將這個數放到結果陣列中,然後該指標向後挪一位。
回到本題,要求醜數ugly陣列中的第n項,而目前只知道ugly[0]=1,所以此時三個有序連結串列分別就只有一個元素:
A : {1*2......}
B : {1*3......}
C :{1*5......}
假設三個陣列的指標分別是i,j,k,此時均是指向第一個元素,然後比較A[i],B[j]和C[k],得到的最小的數A[i],就是ugly[1],此時ugly就變成{1,2}了,對應的ABC陣列就分別變成了:
A : {1*2,2*2......}
B : {1*3, 2*3......}
C :{1*5,2*5......}
此時根據合併有序陣列的原理,A陣列指標i就指向了下一個元素,即'2*2',而j和k依然分別指向B[0]和C[0],然後進行下一輪合併,就是A[1]和B[0]和C[0]比較,最小值作為ugly[2].....如此迴圈n次,就可以得到ugly[n]了。
此外,注意到ABC三個陣列實際上就是ugly[]*2,ugly[]*3和ugly[]*5的結果,所以每次只需要比較A[i]=ugly[i]*2,B[j]=ugly[j]*3和C[k]=ugly[k]*5的大小即可。然後誰最小,就把對應的指標往後移動一個,為了去重,如果多個元素都是最小,那麼這多個指標都要往後移動一個。
class Solution def nthUglyNumber(self, n: int) -> int: dp = [1] * n a, b, c =0 ,0 ,0 for i in range(1, n): dp[i] = min(dp[a]*2, dp[b]*3, dp[c]*5) if dp[i] == dp[a]*2: a += 1 #為什麼a要加1,這是為了遞增a if dp[i] == dp[b]*3: b += 1 if dp[i] == dp[c]*5: c += 1 return dp[-1]
時間複雜度O(N)
空間複雜度O(1)
參考題解:https://leetcode-cn.com/problems/chou-shu-lcof/comments/250364/