面試題精選:數據僞造

這道題應該算是我原創的的一道題,來源於我遇到的一個具體需求。大體需求是已知一批數和每一個數出現的次數,而後寫個接口,每次調用都能返回已知數據中的某個數,且返回的機率和原始數據中每一個數出現的機率一致,題目描述起來有些繞口,咱們來舉個實際的例子。
在這裏插入圖片描述
以上面的輸入爲例,要求實現的接口必須以11.96%的機率返回五、18.10%的機率返回91……16.55%的機率返回98,固然個人要求不單單是這幾個數,而是可能有10^5個數。 先別急着往下看,給你幾分鐘先思考下。java

各類語言其實都內置了random函數,能夠隨機返回int或者long型的隨機數,這裏咱們先不考慮溢出的問題。爲了方便講解,假設咱們已有n個數存在在num[n]中,其出現的頻次存放在fre[n]中。 藉助已有的random(),咱們很簡單就能夠生成0-n之間的一個隨機數i,可是若是直接返回num[i]的話,每一個數返回的機率是一致的,明顯不知足咱們的需求。dom

其實解決方案也很簡單,咱們按照每一個數出現的頻次大小,將其映射成不一樣的區間大小,出現的機率越大,區間越大。想象下,這些數據按不一樣的區間大小把一個飛鏢盤分紅不一樣的部分,咱們生成數的時候就是拿個飛鏢隨機扎,扎到哪一個算哪一個。
在這裏插入圖片描述
固然咱們能夠直接用一位直線區間描述上面的二維飛鏢盤模型。只須要隨機生成0-100%之間的數便可,假設某次隨機生成的數是0.65(65%),咱們算一下 正好對應在數字58對應的區間上,因此此次直接返回58就是了,咱們能夠開始寫代碼了。
在這裏插入圖片描述函數

int[] num; // 數字
    int[] fre; // 出現的頻次
    double[] pro;  // 出現的機率
    int n;  // 數據量
    void init() {
        int sum = 0;
        for (int i = 0; i < n; i++) {
            sum += fre[i];
        }
        for (int i = 0; i < n; i++) {
            pro[i] = fre[i]/sum; // 計算出每一個數出現的機率 
        }
    }
    
    int getRandom() {
        double rp = random.getNextDouble();
        double sum = 0;
        for (int i = 0; i < n; i++) {
            if (sum >= r && sum + pro[i] > rp) {  //找到命中的區間
                return num[i]; 
            }
            sum += pro[i];
        }
        return num[n-1];
    }

彷佛一切都很完美,但每次getRandom()的時間複雜度是O(n),大量的使用性能也抗不太住。有沒有更好的實現方式?既然寫到這裏了,必然是有的。性能

上面代碼循環中有個sum += pro[i]; 每次計算都要累加,咱們是否是能夠提早在init()中累加好?而後你會發現由於每次累加的數都只正數,因此pro是個遞增序列,對於有序序列的查找 二分必然是首選。這時候咱們能夠用二分重寫上面代碼。.net

int[] num; // 數字
    int[] fre; // 出現的頻次
    double[] pro;  // 出現的機率
    int n;  // 數據量
    void init() {
        int sum = 0;
        for (int i = 0; i < n; i++) {
            sum += fre[i];
        }
        for (int i = 0; i < n; i++) {
            pro[i] = fre[i]/sum; // 計算出每一個數出現的機率
            if (i != 0) {
                pro[i] += pro[i-1];
            }
        }
    }

    int getRandom() {
        double rp = random.getNextDouble();
        int l = 0;
        int r = n-1;
        while (l != r) {   // 二分查找肯定區間位置  
            int mid = (l + r) >> 1;
            if (pro[mid] < rp) {
                l = mid + 1;
            } else {
                r = mid;
            }
        }
        return num[n-1];
    }

到這裏問題就完全解決了,可是最後給你們留下一個思考題。code

上述代碼中pro[]的計算有必要嗎? 可否直接用fre[]替代其功能?
本文來自https://blog.csdn.net/xindooblog

相關文章
相關標籤/搜索