Given a function rand7
which generates a uniform random integer in the range 1 to 7, write a function rand10
which generates a uniform random integer in the range 1 to 10.html
Do NOT use system's Math.random()
.git
Example 1:github
Input: 1
Output: [7]
Example 2:dom
Input: 2
Output: [8,4]
Example 3:函數
Input: 3
Output: [8,1,10]
Note:post
rand7
is predefined.n
, the number of times that rand10
is called.
Follow up:學習
rand7()
function?rand7()
?
這道題給了咱們一個隨機生成 [1, 7] 內數字的函數 rand7(),須要利用其來生成一個能隨機生成 [1, 10] 內數字的函數 rand10(),注意這裏的隨機生成的意思是等機率生成範圍內的數字。這是一道頗有意思的題目,因爲 rand7() 只能生成1到7之間的數字,因此 8,9,10 這三個無法生成,那麼怎麼辦?大多數人可能第一個想法就是,再用一個唄,而後把兩次的結果加起來,範圍不就擴大了麼,擴大成了 [2, 14] 之間,而後若是再減去1,範圍不就是 [1, 13] 了麼。想法不錯,可是有個問題,這個範圍內的每一個數字生成的機率不是都相等的,爲啥這麼說呢,咱們來舉個簡單的例子看下,就好比說 rand2(),咱們知道其能夠生成兩個數字1和2,且每一個的機率都是 1/2。那麼對於 (rand2() - 1) + rand2()呢,看一下:優化
rand2() - 1 + rand()2 = ? 1 1 1 1 2 2 2 1 2 2 2 3
咱們發現,生成數字範圍 [1, 3] 之間的數字並非等機率大,其中2出現的機率爲 1/2,1和3分別爲 1/4。這就不隨機了。問題出在哪裏了呢,若是直接相加,不一樣組合可能會產生相同的數字,好比 1+2 和 2+1 都是3。因此須要給第一個 rand2() 升一個維度,讓其乘上一個數字,再相加。好比對於 (rand2() - 1) * 2 + rand2(),以下:this
(rand2() - 1) * 2 + rand()2 = ? 1 1 1 1 2 2 2 1 3 2 2 4
這時右邊生成的 1,2,3,4 就是等機率出現的了。這樣就經過使用 rand2(),來生成 rand4()了。那麼反過來想一下,能夠經過 rand4() 來生成 rand2(),其實更加簡單,咱們只需經過 rand4() % 2 + 1 便可,以下:spa
rand4() % 2 + 1 = ? 1 2 2 1 3 2 4 1
同理,咱們也能夠經過 rand6() 來生成 rand2(),咱們只需經過 rand6() % 2 + 1 便可,以下:
rand6() % 2 + 1 = ? 1 2 2 1 3 2 4 1 5 2 6 1
因此,回到這道題,咱們能夠先湊出 rand10*N(),而後再經過 rand10*N() % 10 + 1 來得到 rand10()。那麼,只須要將 rand7() 轉化爲 rand10*N() 便可,根據前面的講解,咱們轉化也必需要保持等機率,那麼就能夠變化爲 (rand7() - 1) * 7 + rand7(),就轉爲了 rand49()。可是 49 不是 10 的倍數,不過 49 包括好幾個 10 的倍數,好比 40,30,20,10 等。這裏,咱們須要把 rand49() 轉爲 rand40(),須要用到 拒絕採樣 Rejection Sampling,總感受名字很奇怪,以前都沒有據說過這個採樣方法,刷題也是個不停學習新東西的過程呢。簡單來講,這種採樣方法就是隨機到須要的數字就接受,不是須要的就拒絕,並從新採樣,這樣還能保持等機率,具體的證實這裏就不講解了,博主也不會,有興趣的童鞋們能夠去 Google 一下~ 這裏直接用結論就好啦,當用 rand49() 生成一個 [1, 49] 範圍內的隨機數,若是其在 [1, 40] 範圍內,咱們就將其轉爲 rand10() 範圍內的數字,直接對 10 去餘並加1,返回便可。若是不是,則繼續循環便可,參見代碼以下:
解法一:
class Solution { public: int rand10() { while (true) { int num = (rand7() - 1) * 7 + rand7(); if (num <= 40) return num % 10 + 1; } } };
咱們能夠不用 while 循環,而採用調用遞歸函數,從而兩行就搞定,叼不叼~
解法二:
class Solution { public: int rand10() { int num = (rand7() - 1) * 7 + rand7(); return (num <= 40) ? (num % 10 + 1) : rand10(); } };
咱們還能夠對上面的解法進行一下優化,由於說實話在 [1, 49] 的範圍內隨機到 [41, 49] 內的數字機率仍是挺高的,咱們能夠作進一步的處理,就是當循環到這九個數字的時候,咱們不從新採樣,而是作進一步的處理,將採樣到的數字減去 40,這樣就至關於有了個 rand9(),那麼經過 (rand9() - 1) * 7 + rand7(),能夠變成 rand63(),對 rand63() 進行拒絕採樣,獲得 rand60(),從而又能夠獲得 rand10()了,此時還會多餘出3個數字,[61, 63],經過減去 60,獲得 rand3(),再經過變換 (rand3() - 1) * 7 + rand7() 獲得 rand21(),此時再次調用拒絕採樣,獲得 rand20(),進而獲得 rand10(),此時就只多餘出一個 21,重複整個循環的機率就變的很小了,參見代碼以下:
解法三:
class Solution { public: int rand10() { while (true) { int a = rand7(), b = rand7(); int num = (a - 1) * 7 + b; if (num <= 40) return num % 10 + 1; a = num - 40, b = rand7(); num = (a - 1) * 7 + b; if (num <= 60) return num % 10 + 1; a = num - 60, b = rand7(); num = (a - 1) * 7 + b; if (num <= 20) return num % 10 + 1; } } };
Github 同步地址:
https://github.com/grandyang/leetcode/issues/470
相似題目:
Generate Random Point in a Circle
參考資料:
https://leetcode.com/problems/implement-rand10-using-rand7/
https://leetcode.com/problems/implement-rand10-using-rand7/discuss/152282/C%2B%2B-2-line