面試問題:發一個隨機紅包,100塊錢給10個人。每個人最多12塊錢,最少6塊錢。怎么分?


以前想過一個類似問題,就是沒有每個人最大、最小的得錢數的限制,以前的問題可以很好用隨機數解決。

於是這個問題也被以前的思想帶坑里了,把突破口完全放在了如何處理每個人的隨機數上。

於是在面試時間就沒有解決這個問題,直到面試結束自己安靜下來,仔細想想,發現思路錯了。

我認為正確的思路是:每個人先得6塊錢,這樣剩下40塊錢,之后每次拿出一塊錢,隨機分配給一個人,如果某個人的錢數達到了上限,那么這個人下次就沒有了再得到錢的資格了。這樣直到剩下錢都分配完。

當然在接口的實際處理上可以做些優化,例如剩下的錢每次隨機分配的錢可以是隨機的(當然這個隨機要做一些限制,以免一下就分配超額了),然后如果某個人錢+這次隨機分配的錢>每個人的上限,那么他就沒有資格得到這個錢了。

隨機分配也好實現,先算有幾個人有資格得到這筆錢,隨即一個數,決定給第幾個符合資格的人。

我的思路就是這樣,大家如果有更好的思路,請告知。謝謝。

 

$cash = 40;
$user_arr = array(6,6,6,6,6,6,6,6,6,6);
while($cash>0){
    $user_id = rand(0, 9);
    if($user_arr[$user_id]<12){
        $user_arr[$user_id]++;
        $cash--;
    }
}

;
var_dump($user_arr,array_sum($user_arr));die;
性能篇
$arr1=range(2,6);
shuffle($arr1);
$arr2=range(2,6);
shuffle($arr2);
$user_arr = array(6,6,6,6,6,6,6,6,6,6);
 for ($i=0;$i<10;$i++){
     
     if($i<=4){
         $user_arr[$i] += $arr1[$i];
     }else{
         $j = $i%5;
         $user_arr[$i] += $arr2[$j];
         
     }
 }
var_dump($user_arr,array_sum($user_arr));die;

 function rand_red($min,$max,$num,$count){
      $return=[];
      $shenyu=$count-($min*$num);//每個人分6塊剩余的金額
       $rand_num=$max-$min;//最大分配
       $mt_rand_min=1;//剩余金額最小值 默認分1
      for($i=1;$i<=$num;$i++){
        $num_count=mt_rand($mt_rand_min,$rand_num);//隨機分配金額 
        if($shenyu>$num_count){
       $shenyu=$shenyu-$num_count;
       $mt_rand_min=$shenyu/($num-$i);//計算剩余小值
       $red_num=$min+$num_count;//最少分配加上 max-min隨機值
       $return[]=$red_num;
    }else{
      if($shenyu!==0){
      $red_num=$min+$shenyu;
      $shenyu=0;
       $return[]=$red_num;
      }else{
       $return[]=$rand_num;
    }
    }
    }
    return $return;
    }
    $arr=rand_red(6,12,10,100);
    var_dump($arr);
    var_dump(array_sum($arr));

借鑒了樓主思路  英文不好 變量命名不是很標准 別噴~ 期待更好隨機性解析代碼

<?php
//總錢數
$allMoney = 100;
//人數
$peopleNum = 10;
//紅包結果
$result = [];
//隨機生成10個紅包
for ($i=0;$i<$peopleNum;$i++){
    $result[$i] = mt_rand(6,12);
}
//取結果總錢數差
$diff = array_sum($result) - $allMoney;
$absDiff = abs($diff);
//多退少補
for($i=0;$i<$absDiff;$i++) {
    foreach ($result as $key => $value) {
        if ($diff > 0) {
            $value--;
            if ($value >= 6) {
                $result[$key] = $value;
                break;
            }
        } elseif ($diff < 0) {
            $value++;
            if ($value <= 12) {
                $result[$key] = $value;
                break;
            }
        } else {
            break 2;
        }
    }
}

//輸出紅包結果
var_dump($result);
//輸出紅包總錢數
var_dump(array_sum($result));

可能寫復雜了,突然想到的就這樣了。

還有更簡單粗暴的,效率也還行。

<?php
function makePaper()
{
    $result = [];
    for ($i=0;$i<10;$i++){
        $result[$i] = mt_rand(6,12);
    }
    
    if (array_sum($result) != 100) {
        return makePaper();
    }
    
    return $result;
}

最后就是精確到分為單位的,上面兩種方法都可以這么改造。除了人數所有數量乘以一百,運算完之后再除以一百。

<?php

function makePaper($allMoney = 100, $peopleNum = 10, $min = 6, $max = 12)
{
    $result = [];
    for ($i=0;$i<10;$i++){
        $result[$i] = mt_rand($min*100,$max*100);
    }
    
    if (array_sum($result) != $allMoney*100) {
        return makePaper();
    }
    
    return array_map(function($money){
        return bcdiv($money,100,2);
    },$result);
}

$result = makePaper();
var_dump($result);
var_dump(array_sum($result));

寫這么多還被踩,也不給個理由。什么情況?


我從題目中獲取的信息是這樣的:
1、總數是100;
2、10個人分;
3、最小6塊;
4、最大12塊;
5、紅包分完(例如都是6塊的情況不存在)。
思路:
先從總數中扣除最小紅包,保證每個紅包的最小基數,設置一個獎金池,獎金池等於總是減去保底紅包;
每個人實際領到的紅包 等於 紅包基數 加上 從獎金池中獲取的隨機獎金;

隨機獎金會判斷當前獎金池數量 與 剩余人數之間的關系,決定最小獎金的大小:minSignal 
① restNum * range <= restPool : 每個人得到最大獎金依然沒有(剛好)分配完獎金:retrun range;
② (restNum-1) * range > restPool : 判斷下一次剩余人員能否分配完獎金池,如果能,則本次隨機區間在[0,range]
③ restNum range > restPool > (restNum-1) range : 不能,則隨機區間在[restPool % range,range]

function demo(total, min, max, num){
  var pool = total - min * num;
  var restNum = num ; // 剩余人數
  var restPool = pool; // 剩余獎金

  var minSignal = function(){ 
    var range = max - min; // 最大獎金
    return restNum * range > restPool ? (restNum-1) * range > restPool ? 0 : restPool % range : range ;
  };

  var dispatch = function (){
    var minS = minSignal();
    var temp = minS + Math.random() * ( max - min - minS);
    temp = temp > restPool ? restPool : temp; 
    restPool -= temp;
    return min + temp;
  }

  for(var i = 0; i < num; i++){  
    var prize = dispatch(); 
    restNum --; 
    console.log("第"+ (i+1) +"個人:" + prize ,"剩余獎金池:" + restPool);
  }
}

// 測試
demo(100, 6 , 12, 10)
2016-07-20 12:57:29.056 VM9998:19 第1個人:8.917007956230712 剩余獎金池:37.08299204376929
2016-07-20 12:57:29.056 VM9998:19 第2個人:11.711160108665778 剩余獎金池:31.371831935103508
2016-07-20 12:57:29.056 VM9998:19 第3個人:9.60431933144148 剩余獎金池:27.767512603662027
2016-07-20 12:57:29.057 VM9998:19 第4個人:9.005495062706432 剩余獎金池:24.762017540955597
2016-07-20 12:57:29.057 VM9998:19 第5個人:6.881776388287364 剩余獎金池:23.880241152668233
2016-07-20 12:57:29.057 VM9998:19 第6個人:11.477396582224884 剩余獎金池:18.40284457044335
2016-07-20 12:57:29.057 VM9998:19 第7個人:11.980543481582266 剩余獎金池:12.422301088861083
2016-07-20 12:57:29.057 VM9998:19 第8個人:10.577151778799255 剩余獎金池:7.845149310061829
2016-07-20 12:57:29.057 VM9998:19 第9個人:10.915993913333269 剩余獎金池:2.92915539672856
2016-07-20 12:57:29.057 VM9998:19 第10個人:8.92915539672856 剩余獎金池:0

我寫了個思路迥異的。。。感覺有點麻煩,不過效率和可擴展性還湊合。

思路:每次分配后,都確定剩余的金錢在合理范圍。
若合理,進行下次分配
否則,重新進行此次分配。

<?php

function hongbao($money, $people, $min, $max)
{
    $result = [];
    for ($i=0; $i < $people; $i++) { 
        do {
            // 1.進行本次分配
            $result[$i] = mt_rand($min*100, $max*100) / 100; 
            // 2.本次分配后,剩余人數
            $restPeople = $people - ($i+1);    
            // 3.本次分配后,剩余金錢
            $restMoney  = $money - array_sum(array_slice($result, 0, $i+1)); 
            // 4.本次分配后,剩余金錢是否在合理范圍? 不在則重新分配
        } while ($restMoney > $restPeople * $max || $restMoney < $restPeople * $min);
    }
    return $result;
}

$result = hongbao(100, 10, 6, 12);
// 驗證
var_dump($result);
var_dump(array_sum($result));

運行結果:


<?php

function hongbao($money, $people, $min, $max) {
    if($people * $min > $money || $people * $max < $money) {
        return false;
    }
    $result = [];
    for($i = 0;$i < $people; ++$i) {
        if($i == $people - 1) {
            $result[$i] = $money - array_sum($result);
            break;
        }
        $rand = mt_rand($min * 100, $max * 100)/100;
        while(!isset($result[$i])) {
            $restMoney = $money - array_sum($result) - $rand;
            if($restMoney > ($people - $i -1) * $max) {
                $rand += 1;
                $rand > $max && $rand = $max;
            } elseif($restMoney < ($people - $i - 1) * $min) {
                $rand -= 1;
            } else {
                $result[$i] = $rand;
            }
        }
    }
    return $result;
}

$result = hongbao(100, 10, 6, 12);
print_r($result);
print_r(array_sum($result));

?>
整個算法時間復雜度比較穩定


如題,首先要獲取大於6小於12的隨機數,那么只要我們隨機出0-6的隨機數,並且加上6,就是符合要求的。
然后這個是紅包,按照微信紅包的需求來設計,那么隨機數是有兩位有效小數的。那么我們需要隨機出0-600的隨機數,然后除以100。
因為這種設計,所以隨機出來的數值一定大於6,所以6這邊邊際問題,就解決了,只需要考慮12的情況。

隨機出來一個數字,只要確保后面的n位數字的平均值不大於600就可以。


$sum = 0;                 //生成隨機數的和
$total = 4000;           //隨機數總和不能大於4000
$num = 10;                //生成num個隨機數
$factor_start = 0;             //優化算法效率,在一定情況下,提高系數,可以隨機數的效率。
$factor_end = 600;             //后面能隨機的值不夠時,需要控制后面隨機數的范圍。
$rand_num = array();
foreach($i==1;$i<=$num;$i++){
    if($i==10){
      $rand_num[9] = 6 + ($total - $sum)/100;
      break;
    }
    do{
        $rand = mt_rand($factor_start,$factor_end);
        $tmp_sum = $sum + $rand;
        if((($total - $tmp_sum) / ($num - $i)) < 600){
            $sum  = $tmp_sum;
            $rand_num[] = 6 + $rand / 100;
            if($total - $sum<=600){
                $factor_end = $total - $sum;
            }
            break;
        }else{
            $factor_start += 100;
        }
    }while(true)
}
var_dump(shuffle($rand_num));                //重新打亂數組,並輸出

算法就是這樣,結果一定是正確的。
算法中添加的$factor_start和$factor_end變量,就是為了提高算法效率。
假設前面的隨機數都很小,那么后面的隨機數就要大起來。這個時候就需要增大$factor_start,減少后面while循環次數,提高算法效率。

假設前面的隨機數特別大,讓后面的數,無法滿足0,到600的隨機,那么就通過$factor_end來控制。


這個問題很簡單,100塊給10個人,那么平均數就是10,先隨機一個6到12的數,如果小於10,那么剩下的錢除以9肯定平均數大於10,那就在10到12隨機一個數,然后把剩下的錢除以8,如果平均數大於10繼續在10到12里面隨機,如果小於10,那么就在6到10之間隨機,由此類推得到10個隨機數。然后把這10個隨機數打亂分給10個人。


我的想法是采用遞歸來實現,代碼如下:

 
//rem,當前遞歸層次還有多少個數沒有生成,$total生成總量,在這個項目中為40,$must必須達到的數據,$arr,臨時變量用來保存和傳遞用
  //返回類型,$arr,即生成的隨機數組
function test($rem, $total, $must, $arr)
      {
          if($rem>=2)
          {
              $rem -= 1;
              //$min本輪生成隨機數的最小值是多少
              $min = round($must - $rem * 6 , 2);
              if($min<=0)
                  $min =0;
              $max = ($must > 6) ? 6 : $must;
              $rand = round(mt_rand($min*100, $max*100)/100 , 2);
              $arr[] = $rand;              
              $must = $must - $rand;
              echo "生成rand數值:".$rand;
              echo "--剩余隨機次數:".$rem."----必須達成數據:".$must;
              echo "<br>";
              return test($rem, $total, $must, $arr);
          }else{
              $arr[] = $must;
              return $arr;
          }

      }
      $arr = array();
      $brr = test(10, 40, 40,$arr);
      //以后如果我想得到5個人分20塊錢,也可以直接調用
      //test(5,20,20,$arr)即可
      print_r($brr);
      

最后生成的結果如下 :

生成rand數值:0.41--剩余隨機次數:9----必須達成數據:39.59
生成rand數值:0.81--剩余隨機次數:8----必須達成數據:38.78
生成rand數值:5.72--剩余隨機次數:7----必須達成數據:33.06
生成rand數值:2.51--剩余隨機次數:6----必須達成數據:30.55
生成rand數值:1.25--剩余隨機次數:5----必須達成數據:29.3
生成rand數值:5.34--剩余隨機次數:4----必須達成數據:23.96
生成rand數值:5.98--剩余隨機次數:3----必須達成數據:17.98
生成rand數值:5.99--剩余隨機次數:2----必須達成數據:11.99
生成rand數值:6--剩余隨機次數:1----必須達成數據:5.99
Array ( [0] => 0.41 [1] => 0.81 [2] => 5.72 [3] => 2.51 
[4] => 1.25 [5] => 5.34 [6] => 5.98 [7] => 5.99 [8] => 6 [9] => 5.99 )

相當於生成 10 個 [0, 6] 之間的隨機數,並且讓它們和為 40,然后再每個數加上 6 即可。

如果用 Python,可以這樣實現:

import random

r = [6] * 10
for i in range(40):
    r[random.choice([i for i in range(10) if r[i] < 12])] += 1
print(r)

X為已經抽取的數值
Y為已經抽取的人數

思路:
因為100塊分10個人,可選范圍是6到12。所以可以隨機地在6~12分給全部人就可以了。但可能會出現派不完或者不夠分的情況,所以實際上每個人的選擇區間不一定是6~12,也取決於先抽到錢的人。假如他們都抽到的金額接近總體平均值,那么這個區間就會保持在6~12,假如連續開頭三個人都抽到6,那么第四個人的區間就會縮小,下限會提高。然而一旦她抽到了12,又會讓下一位的選擇區間變大了一點。但總體來看,越接近尾聲,選擇區間會縮小,直到最后一個人選擇時,他的選擇上限和下限是恰好相等的。所以用圖來描述這個動態區間,比較有意思的是像一種時間線流動一樣,從最底三層都是6~12,6~12,6~12,然后后面會隨着具體的數值發生變化,你也永遠無法知道下一個數是什么。

這里滿足四個條件:
1.剩余金額除以人數不能大於12
2.剩余金額除以人數不能小於6
3.每個人都只能在6~12里選
4.總金額100分為10個人

m為總金額,n為人數,min選擇下限,max為選擇上限,用方程式

方程式為 min <= (m-x-z)/(n-y-1) <= max,配平就可以

下面是已經配平的式子,式子分別為上限和下限。上限可大於12時,配成12,否則保留。下限同理。

我不懂php,用python來代替:
(我的代碼寫得很不pythonic請不要吐槽,位數采用默認的很多位小數,根據實際情況再進行削減)

#encoding=utf8

from random import uniform

def flag(m, n, min, max):
    x = 0
    y = 0
    L = []
    for i in range(n):
        top = 12
        bottom = 6
        if not m-x-min*(n-y-1) >= 12:
            top = m-x-min*(n-y-1)
        if not m-x-max*(n-y-1) <= 6:
            bottom = m-x-max*(n-y-1)
        next = uniform(bottom, top)
        x += next
        y += 1
        L.append(next)
    return L
    
print flag(100, 10, 6, 12)
print sum(flag(100, 10, 6, 12))

優點是,我得出下一位數永遠時即時運算的,它不是得出一個既定的組合,然后再分配給每一個人。而是根據前面的情況,即時產生下一個結果。在具體用戶上,當然是沒有區別,但我在思考紅包這個玩意的時候,也很自然覺得要是提前分好一個模式給我,那其實下一個數是什么早有了定論,程序員log一下就能知道是什么,那樣就不好玩了。另外紅包派不完的情況下,我無需去記錄已經分配好的模式又在過期后對它進行刪除的操作。這里在隨機前進行判斷來縮小區間,比隨即后判斷是否滿足條件要好那是因為,選擇出來的數符合逐漸變小的區間可能性會越來越低,結果在數據規模更大時,性能會下降嚴重。而先判斷出上下區間則保證整個計算過程長度不變,都是常數級。

缺點是,如果不作另外的解釋和運算,根本不知道上面這是在算什么,思路也不明了。

=================

更新,有評論提到越后抽越多money的問題,是的:

這個是因為人均10元,下限6元比平均低4元,上限12元才高2元,不對稱同時金額分10人最高只有12這個空間“拉得很緊”,所以前三個都是100%在6~12所以無問題,后面就出問題了。這樣有一個問題,就是出現了明顯的規律,越后面越可能大。

畢竟這里均值達到10,每抽到1個人抽到6就需要2個人抽到12來彌補,所以要么讓它更為集中到10,要么分散到2端,同時后面那端要比前者的高很多,而均勻分布是不可能的。因為這里涉及到紅包,紅包分定額和隨機兩種。集中分布可以說是固定金額的近似值,所以一定要做兩端分布,兩端分布可以在區間隨機前對區間進行權重選擇再隨機就能操控,另外,也可以每次隨機都生成10次數據,然后再從中抽取一個出來,其他刪掉,第二個人抽再根據第一個數據生成第二個數列一直到結束,就能做到“分布均勻”,但題目中的數據限制還是很多人會抽到很大的數,的確在所難免。

集中化只要為每個數據根據它的位置乘上相關系數解決。或者直接設為10,然后設定“隨機抖動”= = 。

因為數學的好處所以這個性能完全是常數級的,執行步數每次都是一樣的,不存在要把自己的算法的性能也連同一起和所需生成的數據一起來隨機玩概率的問題。所以想要兩端分布同時隨機分布這里可以在最后生成的答案里加上隨機選一個就能達到效果。但算法之外是這個搶紅包的問題,到底是集中還是分散分布?或許很多人搶時最后的紅包才大還是好事情,搶早了,紅包小了,遲了,被搶光了,最后一個是最危險的也是概率上金額最大的那一個,有意思不?或者說,我喜歡平均一點,既要和某人玩隨機金額才刺激,還要避免某人抽得太小而尷尬?所以最后還得看實際需求怎樣才能決定。

PS:看到題主的評論,題主的思路有人提到是隨機到6塊這種的概率很低,假如真的如此(偷懶沒試過),那算法直覺就是集中化趨勢的過程了。沒有好壞,只是看需求如何。


 <?php
    // 1.初始化,平均分配每人$initAvgMoney(根據限制條件隨機產生)
    // 2.每人隨機拿出一定金額到共享資金池中,進行重新分配
    // 限制條件:$initAvgMoney應滿足條件:"小寶"一分錢也不放入共享資金池("特自私"),其余九人拿出盡可能多的錢到共享資金池(每人只留6元)的情況下,共享資金池平均分配后小寶的錢也不超過12塊

    header("Content-type:text/html;charset=utf-8");
    $minInitAvgMoney = 600;
    // ($maxInitAvgMoney - 600) * 9 / 10 + $maxInitAvgMoney <= 1200;
    $maxInitAvgMoney = floor(1740 / 1.9);
    echo("maxInitAvgMoney:");var_dump($maxInitAvgMoney);
    
    $initAvgMoney = mt_rand($minInitAvgMoney, $maxInitAvgMoney) ;
    echo("initAvgMoney:");var_dump($initAvgMoney);
    
    $maxMinus = $initAvgMoney - 600;
    echo("maxMinus:");var_dump($maxMinus);
    
    
    $moneyArr = array();
    for($i = 0; $i < 10; $i ++){
        $randMinusArr[$i] = mt_rand(0, $maxMinus / 10) * 10;
        // echo("randMinusArr-{$i}");var_dump($randMinusArr[$i]);
        $moneyArr[$i] = $initAvgMoney - $randMinusArr[$i];
    }
    
    
    $randMinusSum = 10000 - $initAvgMoney * 10 + array_sum($randMinusArr);
    
    $avgAddMoney = $randMinusSum / 10;
    
    for($i = 0; $i < 10; $i ++){
        $moneyArr[$i] = ($moneyArr[$i] + $avgAddMoney) / 100;
    }
    
    echo "最終結果:";var_dump($moneyArr);
    echo "結果驗證:";var_dump(array_sum($moneyArr));
    // 感覺還有可以完善的地方,先這樣吧

先每個人分6分,然后把剩下的錢再分
這個時候取10個隨機值(0-9)隨意,然后取各個值在隨機值總和的百分比,分別乘以剩下的10000-60;Math.ceil或者Math.floor都可以
最后一個值防止取ceil時溢出,直接用10000-60-前面九個數的和即可
然后分別加上6,換算即可


根據題主的思路寫了這樣的一個答案

function faHongBao(money, pe, min, max) {
    var sum = money,
        result = [];

    for(var i=0; i<pe; i++) {
        result.push(min);
        sum -= min;
    }

    while(sum > 0) {
        var ran = Math.floor( Math.random() * (pe - 1) );
        if(result[ran] < max) {
            result[ran]++;
            sum--;
        }
    }

    return result;
}

 


一個do while循環就能解決的問題,搞那么復雜。

<?php

$money  = 40;
$people = array_fill(0, 10, 6);

do {

    $lucky_index = mt_rand(0, 9);
    $lucky_money = floatval(substr(mt_rand() / mt_getrandmax(), 0, 4));

    if($people[$lucky_index]+$lucky_money >= 12)
        continue;

    if($money < 1) {
        $m = $money;
    } else {
        $m = $lucky_money;
    }

    $people[$lucky_index] += $m;
    $money -= $m;

} while($money > 0);

print_r($people);

?>

100元,給10人;范圍6-12元
1,每人先發6元。 剩下每人最多還能分到6元。 剩下40元
2,如果按照整元分; 那么等價於40元分到60個槽。。。
3,如果要精確到分, 那么等價於40 00 分 分到 60 * 100個槽。。。


當剩余紅包金額小於等於12 * 剩余紅包個數且大於等於6 * 剩余紅包個數,則將隨機數加入到結果中.

function makeSeq(){
    $n = 10;
    $sum = 100;
    $result = [];
    while ($n > 1) {
        // 6n <= sum <=12n
        $randNum = mt_rand(600,1200) / 100;
        if(($sum-$randNum) >= 6* ($n - 1) && ($sum-$randNum) <= 12* ($n - 1)){
            $sum -= $randNum;
            $n -= 1;
            $result[] = $randNum;
        }
    }
    $result[] = $sum;
    return $result;
}

print_r(makeSeq());
print_r(array_sum(makeSeq()));

7.20更新高新能版

function makeSeq2(){
    $n = 10;
    $sum = 100;
    $result = [];
    for($i=$n;$i>=1;$i--){
        $min = ($sum - 12 * ($i-1))>6?($sum - 12 * ($i-1)):6;
        $max = ($sum - 6 * ($i-1))<12?($sum - 6 * ($i-1)):12;
        $randNum = mt_rand($min,$max);
        $sum -= $randNum;
        $result[] = $randNum;
    }
    return $result;
}

 


樓主的方法只能處理整數問題吧,題目中並沒有交代一定是整數。
每次取隨機的范圍都是變化的
下限從6和(剩余錢數-12*(剩余人數-1))中取大的
上限從12和(剩余錢數-6*(剩余人數-1))中取小的
貼Java代碼

public class Allocation{
    public static final double Total = 100;
    public static final double Min = 6;
    public static final double Max = 12;
    public static final int peopleNum = 10;
    private static double money_left;

    public static double[] allocateMoney(){
        double[] result = new double[10];
        money_left = Total;
        double lowerBound = Min;
        double upperBound = Max;
        double money_now = 0;
        double sum = 0;
        for (int i = 0; i < peopleNum ; i++) {
            lowerBound = Min > money_left - Max*(peopleNum-i-1)?Min:(money_left - Max*(peopleNum-i-1));
            upperBound = Max < money_left - Min*(peopleNum-i-1)?Max:(money_left - Min*(peopleNum-i-1));
            money_now = (upperBound - lowerBound)*Math.random()+lowerBound;
            System.out.println((i+1)+" : " + money_now);
            result[i] = money_now;
            //verify
            sum += money_now;
            money_left = money_left - money_now;
        }
        //verify
        System.out.print("Total = " + sum);
        return result;
    }
    public static void main(String[] args) {
        allocateMoney();
    }
}

某次運行截圖


答案真多,也沒仔細看, 不知道有沒有和我一樣想法的。

我的總體思路是這樣的:先均分6塊;再分析每個人得錢的隨機范圍;每個人依次得錢直至分完,一次就分完了。

1、每人至少6塊,所以先分出60,剩下40。那剩下人的每個人能得到的初看是[0~6];
2、反推。從最后一個人(第10個人)推起,到他時,剩下的錢必定是<=6,繼續往前推一個,到他時,剩下的錢必定是<=12,然后依次類推(最好在紙上寫一下)。這么推我們可以得出到第5個人得錢時,剩下的錢必定是<=36, 那么前4個人必須要分掉至少4塊錢,以此再推,前5個人必定要分掉至少10塊錢,至最后一個至少得分掉34塊錢。這樣通過找規律我們得出了每個人得錢的隨機范圍的第一個值。
3、雖然我們得出了每個人隨機范圍的第一個值,但並不是每一個人的隨機范圍的第二個值都是 6,因為當前面的幾個人已經把大部分錢都分了的時候,后面的人就沒得分了。這次,我們從第一個人開始往后推,當到第N個人時剩下的錢$R<6時,這個人所能分得的錢的隨機范圍第二個值就是剩下的錢$R,然后他后面的人所能分得的錢的隨機范圍的第二個值還是$R;

說明完畢,也不知道有沒有說明白,還是直接上代碼吧:

    $R = 40;    //剩余錢數
    $add_max = 12 - 6;
    $arr = array(6, 6, 6, 6, 6, 6, 6, 6, 6, 6);
    for($i = 0; $i<10; $i++) {
        //默認隨機數是 0~6
        $rand = mt_rand(0, $add_max);
        //反推過程可以得出從第4個人開始,rand要么是以$diff為初始值,要么就是上一行的$rand;而隨機范圍第二個值由剩余錢$R決定。
        if($i > 2) {
            if($R <= 0) break;
            $diff = $R - (10 - $i - 1) * $add_max;
            $rand_end = $R < 6 ? $R : $add_max;
            $diff > 0 && ($rand =  mt_rand($diff, $rand_end));
        }

        $arr[$i] += $rand;
        $R -= $rand;
    }

    var_dump($arr,array_sum($arr));

這里得出的都是整型的,如果要得到浮點數,把rand函數替換成獲得浮點數的隨機函數就行。這是稍微精簡后的,通俗過程:

    $R = 40;
    $add_max = 12 - 6;
    $arr = array(6, 6, 6, 6, 6, 6, 6, 6, 6, 6);
    for($i = 0; $i<10; $i++) {
        //根據反推,前三個是不用考慮隨機范圍的,從第4個人開始才需要考慮隨機范圍
        if($i < 3) {
            $rand = mt_rand(0, 6);
        } else if($i == 3) {
            //留給第5個人的不能超過36。當$rand_start不為0時,說明錢還很充足的。充足到第$i個人必須抽走一些錢才行。
            
            $rand_start = $R - 36 ? $R - 36 : 0;
            $rand = mt_rand($rand_start, 6);
        } else if($i == 4) {
            //留給第6個人的不能超過30.
            $rand_start = $R - 30 ? $R - 30 : 0;
            $rand = mt_rand($rand_start, 6);
        } else if($i == 5) {
            //留給第6個人的不能超過24.到現在為止,錢一定還是充足的,所以不用考慮第二個值。
            $rand_start = $R - 24 ? $R - 24 : 0;
            $rand = mt_rand($rand_start, 6);
        }

        //...

        else if($i == 6) {
            //通過上面得出留給下一個人的不能超過 (10-$i-1)*6;  然后到第7個人時,假設前面每個人都得到了6塊,即只剩下4塊錢時,隨機范圍第二個數就不再是6了,而是剩余數$R
            $rand_start = $R - 24 ? $R - 24 : 0;
            $rand_end = $R > 6 ? 6 : $R;
            $rand = mt_rand($rand_start, $rand_end);
        }

        //...

        $arr[$i] += $rand;
        $R -= $rand;
    } 

困死了,不寫了


$arr = [];
for ($i=0; $i < 9; $i++) { 
    $arr[] = mt_rand(6,12);
}
array_push($arr, 100 - array_sum($arr));
var_dump($arr);
var_dump(array_sum($arr));

數學模型為:取隨機數 x < s,范圍為 [a, b],另一個條件是:
s-x:[(n-1)a, (n-1)b] ==> x: [s-(n-1)a, s-(n-1)b],
其中 s, n, a, b 分別是題中的 金額(100),人數(10),下限(6),上限(12)
php 沒用過,用python的生成器可以簡單的實現:

import random

def hongbao(s, n, a, b):
    for i in range(n-1, -1, -1):
        x = random.randint(max(a, s-b*i), min(b, s-a*i))
        s -= x
        yield x

function roundmoney()
    {
        $cash=100;
    
        for ($i = 0; $i < 10; $i++) {
            $people[$i]=rand(6,12);
            $cash=$cash-$people[$i];
            if($cash==0&&$i==9)
            {
                return $people;
            }
        }
        return false;
    
    }
    while(true)
    {
        if(($res=roundmoney())!=false)
        {
            var_dump($res);
            break;
        }
    
    }

感覺有點粗暴,就是完全讓計算機隨機分,什么時候剛好分夠10個人並且每個人不少於6不大於10 就停止


你的想法基本靠譜,我認為可以這樣分:
1.先每人分6元,還剩下40元。
2.起一個循環,每人分0-(12-他已有的錢)元隨機,直到錢分完為止。
沒有題主講的這么麻煩的。


半夜看到這個問題,忽然想到二分法那種模式,於是搞了一個解決方案,先講思路。
1.先將人分為兩半,左半人分的總金額最少是左半人數x最少金額和總金額-右半人數x最多金額兩個數字中較大的;
2.左半人分的總金額最多是左半人數x最多金額和總金額-右半人數x最少金額兩個數字中較小的;
3.在這個范圍內取隨機數作為分給左半人的金額,然后將問題遞歸為左半人分錢和右半人分錢。
py代碼(直接以分為單位,數字都是整數):

import random

def deliver(sum_of_money,num_of_people,min_of_money,max_of_money):
    if num_of_people==1:
        arr = [sum_of_money]
        return arr
    else:
        half = num_of_people>>1
        border_left = max(min_of_money*half,(sum_of_money-(num_of_people-half)*max_of_money))
        border_right = min(max_of_money*half,(sum_of_money-(num_of_people-half)*min_of_money))
        sum_of_left = random.randint(border_left,border_right)
        arr_left = deliver(sum_of_left,half,min_of_money,max_of_money)
        arr_right = deliver(sum_of_money-sum_of_left,num_of_people-half,min_of_money,max_of_money)
        return arr_left+arr_right
        
list = deliver(10000,10,600,1200)
print list


static void Main(string[] args)
        {
            int all = 100;//總金額
            int person = 10;//人數
            double min = 8;//最小金額
            double max = 12;//最大金額

            double[] array = new double[person];//分配結果集
            int i = 0;//第幾人
            Random ran = new Random();

            //默認分配最小值
            for (i = 0; i < person; i++)
            {
                array[i] = min;
            }

            double yet = min * person;//已分配金額

            int whileTimes = 0;//記錄循環次數
            while (yet < all)
            {
                double thisM = Math.Round((ran.NextDouble() * (max - min - 1)), 2);
                i = ran.Next(0, person);
                if (yet + thisM > all)
                {
                    thisM = all - yet;
                }

                if (array[i] + thisM < max)//判斷是否超出最大金額
                {
                    array[i] += thisM;
                    yet += thisM;
                }
                whileTimes++;
            }


            Console.Write("共循環{0}次\r\n", person + whileTimes);
            yet = 0;
            for (i = 0; i < person; i++)
            {
                yet += array[i];
                Console.Write("第{0}人=>分配{1}元,合計分配{2}元\r\n", i + 1, array[i], yet);
            }
            Console.Read();

        }

<?php
function randMoney($totalMoney, $people, $max, $min)
{
    if ($max * $people < $totalMoney || $min * $people > $totalMoney) {
        throw new Exception("總金錢不可以大於最大值與人數的乘積,不可以小於最小值與人數的乘積", 1);
        return [];
    }

    $minRest   = round(($people * $max - $totalMoney) / ($people - 1), 2) + 0.01;
    $restMoney = $totalMoney;
    $result    = [];
    $time      = 0;
    while ($restMoney) {
        for ($i = 0; $i < $people; $i++) {
            $time++;
            if ($restMoney > 0) {
                if ($restMoney <= $min) {
                    $currenRand = $restMoney;
                } else {
                    $currenRand = $max - $min;
                }
                if ($restMoney <= $minRest) {
                    $rand = $restMoney;
                } else {
                    $rand = mt_rand(0.00, $currenRand * 100) / 100;
                }
                if (isset($result[$i])) {
                    if ($result[$i] + $rand <= $max) {
                        $result[$i] += $rand;
                        $restMoney -= $rand;
                    }
                } else {
                    $result[$i] = $min + $rand;
                    $restMoney -= $result[$i];
                }
                if (!$restMoney) {
                    break;
                }
            }
        }
    }
    return $result;
}

try {
    $result = randMoney(100, 10, 12, 6);
    var_dump($result);
    echo array_sum($result);
} catch (Exception $e) {
    echo $e->getMessage();
}

按照平均法求出X和Y
X+10Y = 100;
X+Y<=12;
故X的值為20/9
(借鑒了上一位回答者的回答)


min = 6;
max = 12;
total = 100;
pe = 10;
maxTotal = max*pe - total;

list = array();
for (0<pe)
    if(maxTotal>0)
        temp = random(0,maxTotal>=6?6:maxTotal);
        maxTotal-=temp;
        list.push(12-temp);
        
if(maxTotal>0)
    pre = maxTotal/pe;
    for (0<pe)list[temp2]+=pre;
 
shuffle(list);

此方法是錯誤的,沒有強迫症,不修改了


這么多人會,就我不會


根據樓主我實現一種

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class RandomMoney {
    public static void main(String[] args) {
        int len = 10;
        float allMoney = 100;//總錢數
        float remainMoeny = 0;//剩余得錢數
        float randomMoney = 0;//隨機生成得錢數![圖片描述][1]
        float sumMoney = 0;//每次隨機生產錢數得總和
        int index; //隨機索引
        float oneMoney;//某一次隨機得錢數
        List<Object> list = new ArrayList<Object>();
        Random  random = new Random();
        for (int i = 0; i < len; i++) {
            list.add(6f);
        }
        sumMoney = sumMoney(list ,len);
        Long star = System.nanoTime();//System.currentTimeMillis();
        while ((remainMoeny = allMoney - sumMoney) > 0) {
            //產生一個剩余錢下的隨機數
            index = random.nextInt(10);
            //當剩余得錢數少於一角 就把剩下得隨機給某一個
            if (remainMoeny < 1f&&remainMoeny > 0) {
                //某一個人的錢數
                oneMoney = (float)list.get(index)+(allMoney-sumMoney);
                if (oneMoney < 12f) {
                    list.set(index, oneMoney);
                    sumMoney = sumMoney(list, len);
                    System.out.println(list);
                    System.out.println(sumMoney);
                }
            }else {
                //隨機生產得錢數
                randomMoney = random.nextInt((int)remainMoeny)+random.nextFloat();
                //某一個人得錢數
                oneMoney = (float)list.get(index)+randomMoney;
                if (oneMoney < 12f) {
                    list.set(index, oneMoney);
                    sumMoney = sumMoney(list , len);
                }
            }
        }
        long end = System.nanoTime();//System.currentTimeMillis();
        System.out.println(end-star);
    }
    public static float sumMoney(List<Object> list ,int len){
        float sumMoney = 0;
        for (int i = 0; i < len; i++) {
            sumMoney += (float)list.get(i);
        }
        return sumMoney;
    }
}

 


function microtime_float(){
    list($usec, $sec) = explode(" ", microtime());
    return ((float)$usec + (float)$sec);
}

function getRandParcent(){
    return rand(1,10)/rand(10,100);  
}


function randUserMoney($cash,$min=6,$max=12){
    $cash_ini = $cash;
    $user_arr = array($min,$min,$min,$min,$min,$min,$min,$min,$min,$min);
    $start = microtime_float();
    while($cash>0){
        $user_id = rand(0, 9);
        $rand_point = getRandParcent();
        if($user_arr[$user_id]<$max){
            $ing = microtime_float();
            if($ing-$start>0.01){
                return randUserMoney($cash_ini);
            }
            $rand_money = round($rand_point*$cash,2);
            $user_money = $user_arr[$user_id]+$rand_money ;
            if($user_money<$max){
                $user_arr[$user_id] = $user_money;
                $cash = $cash - $rand_money;
            }
        }
    }
    return [
    'user_money'=>$user_arr,
    'total_money'=>array_sum($user_arr),
    'excute_time'=>$ing-$start
    ];
}

var_dump(randUserMoney(40));

array (size=3)
  'user_money' => 
    array (size=10)
      0 => float 11.59
      1 => float 9.07
      2 => float 11.99
      3 => float 12
      4 => float 9.14
      5 => float 11.6
      6 => float 11.86
      7 => float 9.93
      8 => float 6
      9 => float 6.82
  'total_money' => float 100
  'excute_time' => float 0.004000186920166

就是每個人都減去6塊錢,這樣問題就轉變成40塊分給10個人,沒人不多於6塊錢。這樣做的核心是,把兩個限制變成了一個限制,因為分到的錢一定是正數,所以大於0這個限制變得簡單了。

剩下的分,在總錢數大於6塊的時候,只要做一個0到6的隨機就可以,小於6塊的時候,做0到這個總數的隨機就可以,最后一個人拿剩下的。


function fhb($money,$num,$min,$max){
    $firstUse=$num*$min;
    $surplusMoney=$money-$firstUse;

    $arr=array();
    for($i=1;$i<=$num;$i++){
        $arr[]=$min;
    }

    $diff=$surplusMoney*100;
    while($diff>0){
        $randUid = rand(0, $num - 1);
        if($arr[$randUid]<$max){
            $arr[$randUid]+=0.01;
            $arr[$randUid]=number_format($arr[$randUid],2,'.','');
            $diff--;
        }
    }
    return $arr;
}

$a=fhb(100,10,6,12);

此算法的特征是每個人分得比較均衡。

Array
(
    [0] => 9.75
    [1] => 9.84
    [2] => 10.06
    [3] => 10.15
    [4] => 9.94
    [5] => 10.17
    [6] => 10.00
    [7] => 10.24
    [8] => 9.86
    [9] => 9.99
)


鏈接



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM