面试问题:关于概率

时间:2011-02-19 16:30:15

标签: algorithm random probability

面试问题:

给定函数f(x)1/4次返回0,3 / 4次返回1。 使用f(x)写一个函数g(x),其中1/2次返回0,1 / 2次返回1.

我的实施是:

function g(x) = {
    if (f(x) == 0){ // 1/4 
        var s = f(x) 
        if( s == 1) {// 3/4 * 1/4
            return s  //   3/16
        } else {
            g(x)
        } 
    } else { // 3/4
            var k = f(x)
            if( k == 0) {// 1/4 * 3/4
                return k // 3/16 
            }  else {
                g(x)
            }       
    }
}

我是对的吗?你的解决方案是什么?(你可以使用任何语言)

10 个答案:

答案 0 :(得分:59)

如果连续两次调用f(x),可能会产生以下结果(假设为 对f(x)的连续调用是独立的,相同分布的试验):

00 (probability 1/4 * 1/4)
01 (probability 1/4 * 3/4)  
10 (probability 3/4 * 1/4)  
11 (probability 3/4 * 3/4)

01和10以相同的概率发生。所以迭代直到你得到其中之一 案例,然后适当地返回0或1:

do
  a=f(x); b=f(x);
while (a == b);

return a;

每次迭代只调用一次f(x)并跟踪两者是很诱人的 最新的价值观,但这不起作用。假设第一卷是1, 概率为3/4。你将循环直到第一个0,然后返回1(概率为3/4)。

答案 1 :(得分:8)

您的解决方案是正确的,如果效率低下且逻辑更复杂。这是一个更简洁的同一算法的Python实现。

def g ():
    while True:
        a = f()
        if a != f():
            return a

如果f()很昂贵,你会希望通过使用匹配/不匹配信息来尝试以较少的调用返回它。这是最有效的解决方案。

def g ():
    lower = 0.0
    upper = 1.0
    while True:
        if 0.5 < lower:
            return 1
        elif upper < 0.5:
            return 0
        else:
            middle = 0.25 * lower + 0.75 * upper
            if 0 == f():
                lower = middle
            else:
                upper = middle

平均约需拨打2.6次g()

它的工作方式是这样的。我们试图从0到1选择一个随机数,但是一旦我们知道数字是0还是1,我们就会立即停止。我们开始知道数字是在区间(0,1)中。 3/4的数字位于间隔的底部3/4,而1/4位于间隔的顶部1/4。我们根据对f(x)的调用决定哪个。这意味着我们现在的间隔较小。

如果我们洗涤,冲洗并重复足够的次数,我们可以尽可能精确地确定我们的有限数,并且在原始间隔的任何区域中具有绝对相等的卷绕概率。特别是我们的卷绕概率大于或小于0.5。

如果你想要,你可以重复这个想法,逐一产生无穷无尽的比特流。事实上,这可以证明是生成这种流的最有效方式,也是信息理论中 entropy 理念的源泉。

答案 2 :(得分:8)

您的算法的问题在于它以高概率重复自身。我的代码:

function g(x) = {
    var s = f(x) + f(x) + f(x); 
    // s = 0, probability:  1/64
    // s = 1, probability:  9/64
    // s = 2, probability: 27/64
    // s = 3, probability: 27/64
    if (s == 2) return 0;
    if (s == 3) return 1;

    return g(x); // probability to go into recursion = 10/64, with only 1 additional f(x) calculation
}

我测量了算法和我的算法的平均次数f(x)。对于您的f(x)计算,每g(x)次计算约为5.3次。使用我的算法,这个数字减少到3.5左右。到目前为止,其他答案也是如此,因为它们实际上与您所说的算法相同。

P.S。:你的定义目前没有提及'随机',但可能是假设。看到我的其他答案。

答案 3 :(得分:3)

Given a function f(x) that 1/4 times returns 0, 3/4 times returns 1

从字面上理解这句话,f(x)如果被调用四次将总是返回零一次和1次3次。这与说f(x)是概率函数不同,并且0到1的比率在许多次迭代中将接近1到3(1/4对3/4)。如果第一种解释是有效的,那么满足条件的f(x)的唯一有效函数,无论您从哪个序列开始,都是序列0111重复。 (或1011或1101或1110,它们是来自不同起点的相同序列)。鉴于这种约束,

  g()= (f() == f())

应该足够了。

答案 4 :(得分:3)

正如已经提到的,你的定义在概率方面并不是那么好。通常这意味着不仅概率良好,而且distribution也是如此。否则你可以简单地写g(x)将返回1,0,1,0,1,0,1,0 - 它将返回50/50,但数字不会是随机的。

另一种欺骗方法可能是:

var invert = false;
function g(x) {
    invert = !invert;
    if (invert) return 1-f(x);
    return f(x);
}

此解决方案将优于所有其他解决方案,因为它只调用f(x)一次。但结果不会很随意。

答案 5 :(得分:3)

对btilly答案中使用的相同方法进行了改进,平均每f()g()调用约1.85次调用(下面记录的进一步细化达到~1.75,大约是2.6,Jim Lewis的接受答案〜5.33)。代码在答案中显得较低。

基本上,我在偶数概率下生成0到3范围内的随机整数:然后调用者可以测试第0位表示第一个50/50值,第1位计算第一个50/50值。原因:1/4和3/4的f()概率比半部分更清晰地映射到四分之一。


算法描述

btilly解释了算法,但我也会以自己的方式这样做......

该算法基本上生成0到1之间的随机实数x,然后根据该数字所在的“结果桶”返回结果:

result bucket      result
         x < 0.25     0
 0.25 <= x < 0.5      1
 0.5  <= x < 0.75     2
 0.75 <= x            3

但是,生成仅给出f()的随机实数很困难。我们必须首先知道我们的x值应该在0..1范围内 - 我们称之为初始“可能的x”空间。然后,我们研究x的实际值:

  • 每次致电f()时:
    • 如果f()返回0(概率为1/4),我们认为x位于“可能的x”空间的下四分之一处,并消除该空间的上四分之三
    • 如果f()返回1(概率为3的4),我们认为x位于“可能的x”空间的上四分之三,并消除该空间的下四分之一< / LI>
    • 当“可能的x”空间被一个结果桶完全包含时,这意味着我们已经将x缩小到我们知道应该映射到哪个结果值并且不需要获取的点x的更具体的值。

考虑这个图表可能有也可能没有帮助: - ):

    "result bucket" cut-offs 0,.25,.5,.75,1

    0=========0.25=========0.5==========0.75=========1 "possible x" 0..1
    |           |           .             .          | f() chooses x < vs >= 0.25
    |  result 0 |------0.4375-------------+----------| "possible x" .25..1
    |           | result 1| .             .          | f() chooses x < vs >= 0.4375
    |           |         | .  ~0.58      .          | "possible x" .4375..1
    |           |         | .    |        .          | f() chooses < vs >= ~.58
    |           |         ||.    |    |   .          | 4 distinct "possible x" ranges

代码

int g() // return 0, 1, 2, or 3                                                 
{                                                                               
    if (f() == 0) return 0;                                                     
    if (f() == 0) return 1;                                                     
    double low = 0.25 + 0.25 * (1.0 - 0.25);                                    
    double high = 1.0;                                                          

    while (true)                                                                
    {                                                                           
        double cutoff = low + 0.25 * (high - low);                              
        if (f() == 0)                                                           
            high = cutoff;                                                      
        else                                                                    
            low = cutoff;                                                       

        if (high < 0.50) return 1;                                              
        if (low >= 0.75) return 3;                                              
        if (low >= 0.50 && high < 0.75) return 2;                               
    }                                                                           
}

如果有帮助,可以一次一个地提供50/50结果的中间人:

int h()
{
    static int i;
    if (!i)
    {
        int x = g();
        i = x | 4;
        return x & 1;
    }
    else
    {
        int x = i & 2;
        i = 0;
        return x ? 1 : 0;
    }
}

注意:这可以通过让算法从考虑f()== 0结果切换到较低的四分之一,以及在上四分之一处进行磨练来进一步调整,基于此平均结算更快地到达结果桶。从表面上看,当上四分之一结果表示立即结果为3时,这对f()的第三次调用似乎很有用,而较低四分之一的结果仍然跨越概率点0.5,因此结果为1和2.当我尝试时,结果实际上更糟。需要进行更复杂的调整才能看到实际的好处,最后我写了一个蛮力的比较,即对第二到第十一次调用g()的低截止值和高截止值。我发现的最好结果是平均值为~1.75,这是由于第一次,第二次,第五次和第八次调用g()寻求低位(即设置low = cutoff)。

答案 6 :(得分:1)

这是一个基于中心极限定理的解决方案,最初是由于我的一位朋友:

/*
Given a function f(x) that 1/4 times returns 0, 3/4 times returns 1. Write a function g(x) using f(x) that 1/2 times returns 0, 1/2 times returns 1.
*/
#include <iostream>
#include <cstdlib>
#include <ctime>
#include <cstdio>
using namespace std;

int f() {
  if (rand() % 4 == 0) return 0;
  return 1;
}

int main() {
  srand(time(0));
  int cc = 0;
  for (int k = 0; k < 1000; k++) { //number of different runs
    int c = 0;
    int limit = 10000; //the bigger the limit, the more we will approach %50 percent
    for (int i=0; i<limit; ++i) c+= f();
    cc += c < limit*0.75 ? 0 : 1; // c will be 0, with probability %50
  }
  printf("%d\n",cc); //cc is gonna be around 500
  return 0;
}

答案 7 :(得分:0)

由于f()的每次返回表示TRUE的概率为3/4,因此使用某些代数我们可以恰当地平衡赔率。我们想要的是另一个函数x(),它返回一个TRUE的平衡概率,所以

function g() {    
    return f() && x();
}

在50%的时间内返回true。

所以让我们找到x(p(x))的概率,给定p(f)和我们想要的总概率(1/2):

p(f) * p(x) =  1/2
3/4  * p(x) =  1/2
       p(x) = (1/2) / 3/4
       p(x) =  2/3

所以x()应该以2/3的概率返回TRUE,因为2/3 * 3/4 = 6/12 = 1/2;

因此以下内容适用于g():

function g() {
    return f() && (rand() < 2/3);
}

答案 8 :(得分:0)

假设

P(f[x] == 0) = 1/4
P(f[x] == 1) = 3/4

并要求函数g[x]具有以下假设

P(g[x] == 0) = 1/2
P(g[x] == 1) = 1/2

我相信g[x]的以下定义就足够了(Mathematica)

g[x_] := If[f[x] + f[x + 1] == 1, 1, 0]

或者在C

int g(int x)
{
    return f(x) + f(x+1) == 1
           ? 1
           : 0;
}

这是基于{f[x], f[x+1]}的调用会产生以下结果的想法

{
  {0, 0},
  {0, 1},
  {1, 0},
  {1, 1}
}

总结我们的每项成果

{
  0,
  1,
  1,
  2
}

其中1的总和表示可能的总和结果的1/2,其他任何总和构成另一个1/2。

编辑。 正如bdk所说 - {0,0}比{1,1}更不可能因为

1/4 * 1/4 < 3/4 * 3/4

然而,我很困惑,因为f[x](Mathematica)的定义如下

f[x_] := Mod[x, 4] > 0 /. {False -> 0, True -> 1}

或者在C

int f(int x)
{
    return (x % 4) > 0
           ? 1
           : 0;
}

然后从执行f[x]g[x]获得的结果似乎具有预期的分布。

Table[f[x], {x, 0, 20}]
{0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0}

Table[g[x], {x, 0, 20}]
{1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1}

答案 9 :(得分:0)

这很像蒙蒂霍尔悖论。

一般而言。

Public Class Form1

    'the general case
    '
    'twiceThis = 2 is 1 in four chance of 0
    'twiceThis = 3 is 1 in six chance of 0
    '
    'twiceThis = x is 1 in 2x chance of 0

    Const twiceThis As Integer = 7
    Const numOf As Integer = twiceThis * 2

    Private Sub Button1_Click(ByVal sender As System.Object, _
                              ByVal e As System.EventArgs) Handles Button1.Click

        Const tries As Integer = 1000
        y = New List(Of Integer)

        Dim ct0 As Integer = 0
        Dim ct1 As Integer = 0
        Debug.WriteLine("")
        ''show all possible values of fx
        'For x As Integer = 1 To numOf
        '    Debug.WriteLine(fx)
        'Next

        'test that gx returns 50% 0's and 50% 1's
        Dim stpw As New Stopwatch
        stpw.Start()
        For x As Integer = 1 To tries
            Dim g_x As Integer = gx()
            'Debug.WriteLine(g_x.ToString) 'used to verify that gx returns 0 or 1 randomly
            If g_x = 0 Then ct0 += 1 Else ct1 += 1
        Next
        stpw.Stop()
        'the results
        Debug.WriteLine((ct0 / tries).ToString("p1"))
        Debug.WriteLine((ct1 / tries).ToString("p1"))
        Debug.WriteLine((stpw.ElapsedTicks / tries).ToString("n0"))

    End Sub

    Dim prng As New Random
    Dim y As New List(Of Integer)

    Private Function fx() As Integer

        '1 in numOf chance of zero being returned
        If y.Count = 0 Then
            'reload y
            y.Add(0) 'fx has only one zero value
            Do
                y.Add(1) 'the rest are ones
            Loop While y.Count < numOf
        End If
        'return a random value 
        Dim idx As Integer = prng.Next(y.Count)
        Dim rv As Integer = y(idx)
        y.RemoveAt(idx) 'remove the value selected
        Return rv

    End Function

    Private Function gx() As Integer

        'a function g(x) using f(x) that 50% of the time returns 0
        '                           that 50% of the time returns 1
        Dim rv As Integer = 0
        For x As Integer = 1 To twiceThis
            fx()
        Next
        For x As Integer = 1 To twiceThis
            rv += fx()
        Next
        If rv = twiceThis Then Return 1 Else Return 0

    End Function
End Class