題目一javascript
竹筒有20根籤,10根白色,10根紅色。抽取10根顏色一致可得到100元獎勵,抽取9根顏色一致可得到50元獎勵,可是抽取紅色5根白色5根就損失50元,問這遊戲是否值得參與?緣由?html
解:java
這是典型的組合數求指望問題。設事件‘抽取10根顏色一致’爲A,事件‘抽取9根顏色一致’爲B,事件‘抽取紅色5根白色5根‘爲C。面試
根據組合數公式數組
C(m.n) = m!/(n!*(m-n)!)
求得事件A的機率P(A) = C(10,10)*2/C(20,10) = 0.00001082508822446903 ;app
事件B的機率P(B) = C(10,9)*C(10,1)*2/C(20,10) = 0.001082508822446903;dom
事件C的機率P(C) = C(10,5)*C(10,5)/C(20,10) = 0.34371820130334063;ide
指望 = P(A)*100 + P(B)*50+P(C)*(-50) = -17.130702115222242;函數
結論,指望低於0。遊戲無數次後,最終會虧損17.13元,該遊戲不值得玩。測試
下面咱們使用程序實現該遊戲,而且提供求指望的函數。
第一個思考,如何高效求出機率,
第二個思考,如何有效地模擬用戶抽籤的狀況,
第三個思考,如何統計抽籤結果呢,
。。。。。
最後想一想,萬一往後用戶須要修改遊戲規則呢
完成整個模擬,須要實現四個模塊:
第一部分,制定遊戲規則和獎勵規則
第二部分,計算期待
第三部分,獲取一次遊戲結果
第四部分,根據獎勵規則,統計結果
打代碼老是很愉快地,啦啦啦~~~
1 function Probability(){ 2 3 //--------------------1,制定規則 4 5 //抽獎箱中的包含的籤種類和數量 6 this.typeColor = { 7 'red': 10, 8 'white':10 9 }; 10 //抽取數量 11 this.extractNum = 10; 12 //可能性,和收益對應表 13 this.possibility = { 14 '10' : 100, 15 '9' : 50, 16 '5' : -50 17 }, 18 19 20 //--------------------2,計算期待 21 this.getExpectation = function(){ 22 23 //暫時省略這一部分代碼,下面咱們一同探討這一部分代碼如何優化 24 return (-17.130702115222242).toFixed(2); 25 }; 26 //-------------------3,獲取一次遊戲結果 27 this.playGame = function(){ 28 var _a = [] ;//空抽獎箱,存儲全部的球 29 var _r = []; //‘抽獎結果’ 30 for( a in this.typeColor){ 31 for(var i=0;i<this.typeColor[a];i++){ 32 _a.push(a); 33 } 34 }//將籤放入抽獎箱 35 36 37 //遍歷,開始抽獎, 38 for(var i=0;i<10;i++){ 39 //抽取特定一個球 40 var _i = Math.round(Math.random()*(_a.length-1)); 41 //將抽出的球,放入‘抽獎結果’中 42 _r.push(_a[_i]); 43 //移除抽獎箱中剛被抽取的球 44 _a.splice(_i,1); 45 } 46 return _r; 47 }; 48 49 //-------------------4,統計結果 50 this.getResult = function(arr){ 51 //統計各類球的結果 52 var _r = {}; 53 for( k in arr){ 54 _r[arr[k]]? _r[arr[k]]++:_r[arr[k]] = 1; 55 } 56 57 //統計收益 58 for(p in _r){ 59 for(k in this.possibility){ 60 if(_r[p] == k){ 61 _r.earnings = this.possibility[k];//價格 62 } 63 } 64 } 65 return _r; 66 } 67 }
下面咱們一塊兒思考求指望這一部分如何逐步優化:
嘗試一,只完成基本的題目要求
1 function probability1(){ 2 //通用C函數 3 //C(m.n) = m!/(n!*(m-n)!) 4 function C(a,b){ 5 var _n=1,_d=1;//分子,分母 6 for(var i=a;i>0;i--) 7 _n *=i; 8 for(var i=b;i>0;i--) 9 _d *=i; 10 for(var i=1,j=(a-b);j>0;i++,j--) 11 _d *= i; 12 return _n/_d; 13 } 14 return C(10,10)*2/C(20,10)*100+//抽取10根相同顏色的機率*100 15 C(10,9)*C(10,1)*2/C(20,10)*50+//抽取9根相同顏色的機率*50 16 C(10,5)*C(10,5)/C(20,10)*(-50);//抽取5根相同顏色的機率*(-50) 17 18 }
仔細觀察,好多組合數是被重複計算的,例如,C(10,10)和C(20,10)等等,能否用一個變量存儲已經計算過的組合數,下次求相同的組合數的時候能夠從靜態變量中獲取而不須要重複計算。
嘗試二,在嘗試一的基礎上避免計算重複的C
1 function probability2(){ 2 //通用C函數 3 function C(a,b){ 4 var _n=1,_d=1; 5 if(!resOfC[a+','+b]){ 6 var _n=1,_d=1;//分子,分母 7 for(var i=a;i>0;i--) 8 _n *=i; 9 for(var i=b;i>0;i--) 10 _d *=i; 11 for(var i=1,j=(a-b);j>0;i++,j--) 12 _d *= i; 13 return _n/_d; 14 15 }else{ 16 return resOfC[a+','+b]; 17 } 18 } 19 20 var resOfC = {}; //一個新的臨時數組,存儲可能出現的結果 21 //若是以前計算過C(10,10),能夠直接從 22 console.info(C(10,10)*2/C(20,10)); 23 console.info(C(10,9)*C(10,1)*2/C(20,10)); 24 console.info(C(10,5)*C(10,5)/C(20,10)); 25 return C(10,10)*2/C(20,10)*100+ 26 C(10,9)*C(10,1)*2/C(20,10)*50+ 27 C(10,5)*C(10,5)/C(20,10)*(-50); 28 29 }
然而,判斷組合數是否重複計算並非重點。有數學公式C(m,n) = C(m,m-n),咱們計算C(10,9)能夠轉換爲計算C(10,1),以提升計算效率。
嘗試三,在嘗試一的基礎上判斷a/2和b的大小,例如對於C(10,9),直接計算C(10,1)
1 function probability3(){ 2 //通用C函數 3 function C(a,b){ 4 var _n=1,_d=1;//分子,分母 5 6 if(b>a/2) 7 b = a-b; 8 9 for(var i=a;i>0;i--) 10 _n *=i; 11 for(var i=b;i>0;i--) 12 _d *=i; 13 for(var i=1,j=(a-b);j>0;i++,j--) 14 _d *= i; 15 return _n/_d; 16 } 17 18 var resOfC = {}; //一個新的臨時數組,存儲可能出現的結果 19 //若是以前計算過C(10,10),能夠直接從 20 return C(10,10)*2/C(20,10)*100+ 21 C(10,9)*C(10,1)*2/C(20,10)*50+ 22 C(10,5)*C(10,5)/C(20,10)*(-50); 23 24 }
學過組合數的同窗都知道,有數學公式C(n,k)=C(n-1,k)+C(n-1,k-1) ,
咱們先從C(0,0)算起,而後C(1,0),C(1,1),C(2,0),C(2,1),C(2,2)......C(n,k),仔細觀察,其實求組合數的問題由交疊的子問題構成。
第一時間想到的是使用遞歸關係包含子問題和大問題具備的相同形式,但因爲子問題具備交疊,用遞歸方法解決代價很大,咱們能夠考慮使用動態規劃來求解。
對每一個交疊的子問題只求解一次,並把結果存儲在記錄表中,最後得出原始問題的解。
嘗試四,利用遞歸和動態規劃來求解組合數
存在C(n,k)=C(n-1,k)+C(n-1,k-1) ,創建一張表,咱們對數據進行動態更新,即每一次迭代,咱們都去根據公式計算出合適的值。
1 function probability3(){ 2 //C(a,b)=C(a-1,b-1)+C(a-1,b) 3 //通用C函數 4 function C(a,b){ 5 if(b==0){ 6 return 1; 7 } 8 var temp = [];// 9 for (var i = 0; i < a; i++) { 10 temp[i] = []; 11 temp[i][0] = 1;//每行首尾爲1 12 temp[i][i+1] = 1;//每行末尾爲1 13 14 for (var j=1;j<=i;j++) { 15 temp[i][j] =temp[i-1][j-1]+temp[i-1][j];////計算第i行第j列的值 16 } 17 } 18 return temp[a-1][b]; 19 } 20 return C(10,10)*2/C(20,10)*100+ 21 C(10,9)*C(10,1)*2/C(20,10)*50+ 22 C(10,5)*C(10,5)/C(20,10)*(-50); 23 }
源碼及遊戲演示測試地址:http://lovermap.sinaapp.com/probability.html
效果圖:
題目二
眼前有兩塊100px*100px的兩塊div,分別距離咱們10m和7m,可是因爲視覺差,咱們只看到最靠近咱們的一塊板。用鼠標模擬咱們的運動軌跡,鼠標右移相似於咱們往右側行走。請模擬實現咱們右移,所看到兩塊div形態變換。
一開始,我腦海中的構想:
假設100px=4cm,即半徑等於85cm=>2125px。因爲時間的關係,就直接把草稿拍照放上來,雅蠛蝶,真的好複雜:
咱們圍繞小圈圈,想看到後面的div。在咱們接近圓邊東方這個點的過程當中,前面的div將會不要斷縮小,然後面的div將會不斷放大。可是鼠標移動npx,而div會放大mpx,須要集合詳細的三角函數和反三角函數計算。好啦沒問題來了,怎麼算D1和D2
因而我開始不斷惡補各類高中三角知識。。。。。
先補一下三角函數的基礎知識
再補充一下javascript關於三角函數的方法
Math.cos(x) X的餘弦函數
Math.sin(x) X的正弦函數
Math.tan(x) X的正切函數
Math.acos(y) X的反餘弦函數
Math.asin(y) X的反正弦函數
Math.atan(y) X的反正切函數
折騰大半個晚上了,爲何還算不出來,難道真的是我理解錯了 好咯好咯~~從新再想一想
秉承「怎麼簡單怎麼算」的原則,我從新理解了一次題目。。。。
10米和7米這兩個數值若是轉換爲像素將不利於計算,出來的效果也不太樂觀,我將它們分別改爲了10cm和7cm了。
嗯嗯,應該是這樣子了
假設人行走a cm,咱們能夠經過a來表示D1和D2的長度,期間使用到類似三角形、三角形面積公式等原理,徹底不須要三角函數和反三角函數。剩下的就不難了
1 var $ = function(id){ 2 return typeof id == 'string' ? document.getElementById(id) : id; 3 }; 4 //計算前面板初始寬度 5 $("box1").style.width = 28/Math.sqrt(65)*30+'px'; 6 (function(o){ 7 if(o){var o = $(o);}else{return false;}; 8 var d = document,x,y; 9 o.onselectstart = function(){return false;}; 10 o.onmousedown = function(e){ 11 e = e || window.event; 12 x = e.clientX-o.offsetLeft; 13 d.onmousemove = function(e){ 14 e = e || window.event; 15 var a = e.clientX - x; 16 17 //小圓點在虛線範圍內可拖拽 18 if(a>=0 && a<90){ 19 o.style.left = e.clientX - x + "px"; 20 var Div1 = 28/Math.sqrt(65+8*a); 21 var Div2 = (9/7)*a/Math.sqrt((9/49)*a*a+9); 22 $("box1").style.width = Div1*30+'px'; 23 $("box2").style.width = Div2*30+'px'; 24 } 25 26 }; 27 d.onmouseup = function(){ d.onmousemove = null; }; 28 }; 29 })('origin');
源碼及演示地址:http://lovermap.sinaapp.com/rotate.html
拖動小黑點->
拖動小黑點->
總結
第一點,面試前千萬不要熬夜,千萬不要熬夜,千萬不要熬夜!!!重要的事情要說三遍,面試答題的時候,腦殼滿滿的睏意和餓意,將會形成悲慘的結局。
第二點,注意要把問題細化,着手解決問題前,應該根據題目所給的條件,將問題拆分紅爲若干小問題,而後針對性地根據逐一解決。
第三點,避免先入爲主。一開始認爲問題很難,不斷深究,最後很容易把本身逼得走投無路。當問題暫時沒法解決的時候,回到最初的問題,從新分析,換一個角度,將會有新想法。
參考連接