leetcode-470.用 Rand7() 实现 Rand10()

数学问题


题目详情

给定方法 rand7 可生成 [1,7] 范围内的均匀随机整数,试写一个方法 rand10 生成 [1,10] 范围内的均匀随机整数。
你只能调用 rand7() 且不能调用其他方法。请不要使用系统的 Math.random() 方法。
每个测试用例将有一个内部参数 n,即你实现的函数 rand10() 在测试时将被调用的次数。请注意,这不是传递给 rand10() 的参数。


示例1:

输入: 1
输出: [2]

示例2:

输入: 2
输出: [2,8]

示例3:

输入: 3
输出: [3,8,10]

思路:
我们先考虑,使用rand2()可以生成[1,2]的随机数,那么如何得到[1,4]呢?
是不是一下想到用rand2()+rand2()呢?
rand2() + rand2() = ? ==> [2,4]
1 + 1 = 2
1 + 2 = 3
2 + 1 = 3
2 + 2 = 4
为了把生成随机数的范围缩小成[1,n],于是在上一步的结果后减1
(rand2()-1) + rand2() = ? ==> [1,3]
0 + 1 = 1
0 + 2 = 2
1 + 1 = 2
1 + 2 = 3
可以看到,这样生成的结果虽然是[1,3]但是概率却不相等,所以是不能用的
那如果我们把(rand()2-1)×2呢?(这里怎么想到的我TM也不知道)我们发现
(rand2()-1) × 2 + rand2() = ? ==> [1,4]
0 + 1 = 1
0 + 2 = 2
2 + 1 = 3
2 + 2 = 4
概率竟然完全相等,我们已经通过rand2()实现了rand4()
于是我们得出一个数学规律:
已知 rand_N() 可以等概率的生成[1, N]范围的随机数
那么:
(rand_X() - 1) × Y + rand_Y() ==> 可以等概率的生成[1, X * Y]范围的随机数
即实现了 rand_XY()
我们再想一个问题:如何反过来通过rand4()实现rand2()呢?我们可以通过取余实现
rand4() % 2 + 1 = ?
1 % 2 + 1 = 2
2 % 2 + 1 = 1
3 % 2 + 1 = 2
4 % 2 + 1 = 1
事实上,只要N是2的倍数,我们都可以通过randN()来实现rand2(),是等概率的!
反之如果N不是2的倍数通过取余是得不到等概率的,如:
rand6() % 2 + 1 = ?
1 % 2 + 1 = 2
2 % 2 + 1 = 1
3 % 2 + 1 = 2
4 % 2 + 1 = 1
5 % 2 + 1 = 2
6 % 2 + 1 = 1
rand5() % 2 + 1 = ?
1 % 2 + 1 = 2
2 % 2 + 1 = 1
3 % 2 + 1 = 2
4 % 2 + 1 = 1
5 % 2 + 1 = 2
有了上面的规律方法,我们可以分析本题了,需要我们通过rand7()实现rand10()
那么我们可以先实现randN()–N为10的倍数,然后取余
我们通过上面规律进行处理:
(rand7()-1) × 7 + rand7() ==> rand49()
但是49不是10的倍数啊!我们可以利用"拒绝采样"(不想要哪个采样结果就拒绝它)
把rand49()产生的41~49全部拒绝掉再取余就好了…

代码:

// The rand7() API is already defined for you.
// int rand7();
// @return a random integer in the range 1 to 7
class Solution 
{
public:
    int rand10() 
    {
       while (true)
       {
           int num = (rand7() - 1) * 7 + rand7(); // 得到[1,49]
           if (num <= 40)   // 拒绝采样,得到[1,40]
           return num % 10 + 1; // 取余得到[1,10]
       } 
    }
};

优化

我们在上面的方法中,"拒绝采样"了41-49,那么我们能不能合理利用这九个数,从而提高生成随机数的效率,我们可以利用41-49减去40得到rand9()进一步得到rand10(),这时候我们发现再次处理后又"拒绝"61-63三个随机数,我们再利用其减去60得到rand3()进一步得到rand10(),这时最后只"拒绝"了21一个数,我们无需再处理,至此结束,提高了随机数的命中率

// The rand7() API is already defined for you.
// int rand7();
// @return a random integer in the range 1 to 7
class Solution 
{
public:
    int rand10() 
    {
      while (true)
      {
          int a = rand7();
          int b = rand7();
          int num = (a - 1) * 7 + b; // rand49()
          if (num <= 40) return num % 10 + 1; //拒绝采样得到rand10()
        //下面的代码处理上面得到的无用的随机数[41,49]
          a = num - 40; // rand49()-rand40()=rand9()
          b = rand7();
          num = (a - 1) * 7 + b; // rand63
          if (num <= 60) return num % 10 + 1; //拒绝采样得到rand10()
        //下面的代码处理上面得到的无用的随机数[61,63]
          a = num - 60; // rand3
          b = rand7();
          num = (a - 1) * 7 + b; // rand21
          if (num <= 20) return num % 10 + 1; //拒绝采样得到rand10()
        //再往下就无法再物尽其用了,因为只剩了一个21了

      }
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ggaoda

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值