google面試題:給定能隨機生成整數1到5的函數,寫出能隨機生成整數1到7的函數。
問題分析:現在給了一個能隨機生成1~5的隨機函數,怎樣利用這個已知條件生成一個1~7的隨機函數呢?既然要生成的是隨機數那么生成1,2,3,4,5,6,7的概率就應該是一樣的。顯然現在光生成1~5之間的數就不夠了,我們想到應該要加大生成數的范圍,並且加大范圍的同時還要保證每個數產生的概率一樣,於是有這樣一種方法用這個表達式來擴大生成數范圍:rand5()*5+rand5(),新的數據范圍變成:6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30.並且可以看出來這個25個數出現的可能性是一樣的,於是我們可以只用6~26之間的21個數變成1~7這7個數,於是就是要每3個數對應一個數,即:
6,7,8對應1
9,10,11對應2
…………
24,25,26對應7
這種變化對應的方式是(6 - 3)/ 3 = 1,(7 - 3) / 3 = 1,(8-3) / 3 = 1.
int rand7() { int i; //直到產生6~26之間的數跳出循環 while((i=rand5()*5+rand5()) > 26) ; return (i-3)/3; }
總感覺這種方式不好,雖然1-7概率均勻,但相加不等於1。現在深入分析:
假設我們要等概率生成一個3位的10進制數(000 - 999),我們可以在 隨機生成整數0到9的函數 基礎上,隨機生成3個數字組成三位數就得到了結果。
這里類似,我們首先必須認識到:任何一個數都可以用5進制的數來表示,如12 = 5進制(22) = 2*5 + 2。因此假設我們要隨機生成[0,444]范圍的數,我們只要隨機生成3個5進制的數字組合就可以。
這里的主要問題是:7不是5的冪次方。
但是我們可以將某一個5的冪次方均分成 7 段(分別為0 - 6,等概率的落到每一段),利用5進制隨機成一個數,看這個數在哪一個段,就代表我們要生成哪一個數字,這樣就保證了等概率的生成0 - 6.
有一個小的問題就是:5的冪次方不能整除7,會遺漏最高的幾個數。但是我們這里只要數字足夠大,則遺漏的概率相當的小。
下面是簡單的代碼:
const int K = 10;//[0, 5^K - 1]
int Base[K],Rnd[K],Step; //Step表每個區間的長度
int Rand15() //已有的隨機生成1 - 5的隨機函數
{
return rand()%5 + 1;
}
void Init()
{
Base[0] = 1;
for(int i=1 ;i<K; ++i)
Base[i] = Base[i-1]*5;
Step = Base[K-1]*5/7;
//cout<<"Step = "<<Step<<"\tStep*7 = "<<Step*7<<endl;
srand(time(0));
}
int Rand17() //我們需要寫的隨機生成1 - 7的隨機函數
{
int Sum = 0;
for(int i=0 ;i<K; ++i) //隨機生成K個1到5的隨機數
{
Rnd[i] = Rand15();
Sum += (Rnd[i]-1)*Base[i];
}
return Sum/Step + 1;
}
這里K = 10的時候,保證遺漏了2個數,即落到余數里面的概率是:2/9765625.
利用rand[1,5】得到rand[1,7]可以抽象成利用rand[0,1]得到rand[0,n],看下面的就清楚了。
-----------------------------------------
題目:已知rand7() 可以產生 1~7 的7個數(均勻概率),利用rand7() 產生rand10() 1~10(均勻概率)
記住這道題重點是:均勻概率。
rand7 產生的數概率是一樣的,即1~7出現概率一樣,由於我們對結果做了一定的篩選只能通過 1~5,而1~5出現的概率也是一樣的,又由於范圍為1~5 所以 temp1 出現 1~5的概率 為1/5 ,同理 后面的 出現 temp2 的概率為 1/2。
首先temp1出現在1~5的概率為1/5,而temp2出現 1~2 的概率為1/2,也就是說 5*(temp2-1) 出現5或0的概率為1/2,所以假如你要得到1~5的數的話 那么 5*(temp2-1) 必須0,所以因為你要保證 5*(temp2-1)=0,這個概率只有1/2,再加上 你前面指定1~5 的概率 為1/5 ,所以結果為 1/5*1/2=1/10
int rand10() 02 { 03 int temp1; 04 int temp2; 05 do 06 { 07 temp1 = rand7(); 08 }while(temp1>5); 09 do 10 { 11 temp2 = rand7(); 12 }while(temp2>2); 13 return temp1+5*(temp2-1); 14 }
已知一個函數f可以得到1-5間的隨機數,問怎么得到1-7的隨機數
對不對?
這個其實和算法導論上的一個題很像么:已知random等概率返回0或者1,那么試寫一個函數等概率返回[a,b]之間的整數。思路就是2進制表示[0, b-a]之間的數,先計算出至少需要多少位,按位生成一個二進制數,一旦大於b-a就重新生成。放到這里的話,表示成5進制就可以了~
推廣一下:對於等概率可以生成k個連續整數函數的函數randomk,設計生成[a,b]之間的整數的算法:
令n = b - a;則等概率生成[0,n]上的一個整數即可。於是用k進制表示生成的整數,設m=ceiling(logk(n)),(這個不對)
k^m-1=n; 求解m。m=ceil(logk(n+1)); 向上取整,如4,應該用3個二進制表示. log2(5) 向上取整. (m還可以寫成:
m= 1 +log2n)
- int randN() {
- while (res > n) {
- for (i = 0; i < m; i++)
- gen bit i for res with randomk
- }
- return res;
- }
- 期望的運行時間為t*m* i * (1 - (n+1)/k^m)^(i-1) * ((n+1)/k^m),i從1加到無窮,t表示randomk的運行時間,那么計算這個級數的值為t*m*k^m/(n+1).
帶入到這個題目,期望運行時間為50*t/7,還是很快的。
void set(int &a,int i) { a |= (1<<i);}//一定要是引用 int randN(int n) { int res=n+1;//讓第一次進入while循環,res只要》n即可. //c++只有log(默認以e為底)和log10,如果自己設定底數用換底公式 float floatN=n; float tmp=log(floatN+1)/log(2.0); int m=ceil(tmp); //cout<<"m:"<<m<<endl; while(res>n) { res=0; for(int i=0;i<m;i++) { Sleep(20); srand(time(NULL)); int bit=rand()%(2-0)+0;//[0,1] if(bit==1) { set(res,i); } } } return res; }
參考:http://blog.csdn.net/pkueecser/article/details/7425994