你如何生成m个数让其和等于n

今天看到了一个比较有意思的算法题,其实更有意思的是其解法,让人顿时有一种耳目一新的感觉,爱不释手,拿来分享一下。

题目:假设生成26个非负随即数,要求其和是301,求程序生成此列数字

哈哈,元芳,你如何看?

解法一: 关于此种算法原理,我们可以假想是一根长301单位的绳子,然后随即在其上面截25个点,于是得到26根子绳,这26根子绳之和恰好就是绳子总长301。

于是,我们可以:

初始化27的数组

该数组0位和26位分别为0和301,其他位置填充0到301之间的随即数字

对数组排序

数组相邻数字相减,所得的26个差值即为所求数列。

 
 
  1. class Random301   
  2.    {   
  3.        static void Main(string[] args)   
  4.        {   
  5.        int[] arryTemp=new int[27];   
  6.        int[] arryRandom = new int[26];   
  7.        //get random number,from 0 to 301   
  8.        Random ran=new Random((int)DateTime.Now.Ticks);   
  9.        arryTemp[0] = 0;   
  10.        arryTemp[26] = 301;   
  11.        for (int i = 1; i < 26; i++)   
  12.        {   
  13.            arryTemp[i] = ran.Next(0,301);   
  14.        }   
  15.            
  16.        //sort the arry   
  17.        int temp;   
  18.        for (int m = arryTemp.Length-1; m > 0; m--) {   
  19.            for (int n = 0; n < m;n++ ) {   
  20.                if (arryTemp[m] < arryTemp[n]) {    
  21.                     temp=arryTemp[n];   
  22.                     arryTemp[n] = arryTemp[m];   
  23.                     arryTemp[m] = temp;   
  24.                }   
  25.            }   
  26.        }   
  27.        //get the lastest random arry   
  28.        for (int j = 0; j < arryRandom.Length;j++) {   
  29.            arryRandom[j] = arryTemp[j + 1] - arryTemp[j];   
  30.        }   
  31.           
  32.        //check the arry   
  33.        int sum = 0;   
  34.        for (int k = 0; k < arryRandom.Length; k++) {   
  35.            sum = sum + arryRandom[k];   
  36.        }   
  37.        Console.WriteLine(sum);   
  38.        Console.ReadKey();   
  39.    }         
  40.    } 

解决方案二,这种方案利用了非负这个不显眼的条件,思路非常简单,就是将1随即加到一个26位数组中,随即301次,有点剑走偏锋,另辟蹊径,让人耳目一新阿,有谋有啊有谋有!

 
 
  1. class Radom301Arry   
  2.   {   
  3.       static void Main(string[] args) {   
  4.           int[] arryRandom = new int[26];   
  5.           Random ran = new Random();   
  6.           //add 1 301times into the arry   
  7.           for (int i = 0; i <301;i++ ) {   
  8.               arryRandom[ran.Next(026)]++;   
  9.           }   
  10.           //chenck the arry   
  11.           int sum = 0;   
  12.           for (int j = 0; j < arryRandom.Length; j++) {   
  13.               Console.WriteLine(arryRandom[j]);   
  14.               sum = sum + arryRandom[j];   
  15.           }   
  16.           Console.WriteLine("sum:"+sum);   
  17.           Console.ReadKey();   
  18.       }   
  19.   } 

多谢@另一个石头,@八字和尚,@firstrose等等朋友的质疑指证,我测试了一下,如果将数字增大,i <3000001,确实得出来的数字比较的平均,看来我扔了我这块石头确实引来了不少玉啊,嘿嘿,我仔细的看了下,第二种算法确实隐藏着缺陷,此方法的意图是通过利用概率的随机性,等于将301个1按照等概率分布在26个位置,random函数均匀抛出数字,所以其分布应该大概按照301/26分布,在301次的时候其实已经有表现了,当数字变大此种现象更加明显。

不过由于此种算法确实太过奇妙,所以我觉得用于小数字的随机还是可以的,元芳,你怎么看呢?  

第三种解法:下象棋的朋友都知道一种常见的开局方式,兵三进一或者兵七进一,即仙人指路局,此种着法能进能退,能起马能飞炮,算起来中规中矩,其实也不乏一种方法,于是我也中规中矩的按照正常的思路又写了一种所谓的“中规中矩”的算法:

 
 
  1. class List301   
  2.    {   
  3.        static void Main(string[] args) {   
  4.            //store the 26 random numbers   
  5.            List<int> templist = new List<int>();   
  6.            Random ran = new Random((int)DateTime.Now.Ticks);   
  7.            int temp = 0;   
  8.            int total = 301;   
  9.            for (int i = 0; i < 26; i++)   
  10.            {   
  11.                if (25 != i)   
  12.                    temp = ran.Next(0, total);   
  13.                else 
  14.                    temp = total;   
  15.                total = total - temp;   
  16.                templist.Add(temp);   
  17.            }     
  18.              int sum = 0;   
  19.            for (int m = 0; m <templist.Count; m++)   
  20.            {   
  21.                sum = sum + templist[m];   
  22.                Console.WriteLine(m+":"+templist[m]);   
  23.            }   
  24.            Console.WriteLine(sum);   
  25.            Console.ReadKey();   
  26.        }   
  27.    } 

这种方法就是先从0-301之间random出来一个数字,然后301减去此数字得出目前需要抛出数字的总和,然后再从0-目前总合中再random出来一个数字。。。如此知道第26个数字,不再random,直接赋值为最后剩下的目前数字之和,测试后发现这个方法最后会抛出大串的0,也在意料之中,因为随着random次数的增加,random的震荡范围越来越小,最终会在大于0附近徘徊。

鉴于此种现象,稍微改进了一下方案,控制了一下random的震荡范围:

 
 
  1. class List301   
  2.    {   
  3.        static void Main(string[] args) {   
  4.            //store the 26 random numbers   
  5.            List<int> templist = new List<int>();   
  6.            Random ran = new Random((int)DateTime.Now.Ticks);   
  7.            int temp = 0;   
  8.            int total = 301;   
  9.            for (int i = 0; i < 26; i++)   
  10.            {   
  11.                if (25 != i)   
  12.                {   
  13.                    int avg = total / (26 - i);//控制震荡范围在动态平均值附近   
  14.                    temp = ran.Next(0, avg * 2);   
  15.                    total = total - temp;   
  16.                }   
  17.                else 
  18.                    temp = total;   
  19.                templist.Add(temp);   
  20.            }   
  21.    
  22.            //check   
  23.            int sum = 0;   
  24.            for (int m = 0; m <templist.Count; m++)   
  25.            {   
  26.                sum = sum + templist[m];   
  27.                Console.WriteLine(m+":"+templist[m]);   
  28.            }               
  29.            Console.WriteLine(sum);   
  30.            Console.ReadKey();   
  31.        }   
  32.    } 

但是觉得这样并不好,控制了震荡范围,就等于间接控制了随机数字的出现概率,算来算去还是第一种方法和第二种方法好,元芳,你认为呢?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值