面试题:关于概率

Posted

技术标签:

【中文标题】面试题:关于概率【英文标题】:An interview question: About Probability 【发布时间】:2011-06-30 10:24:15 【问题描述】:

面试题:

给定一个函数 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)
                   
    

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

【问题讨论】:

返回 0 / 1 还是打印 0 / 1? 返回。很抱歉造成混乱。 你的函数可能会陷入无限循环 @Dave,可以,但不太可能。 ;p x 的参数是干什么用的?好像没什么用。 【参考方案1】:

btilly 的答案中使用的相同方法的改进,每个g() 结果平均达到约 1.85 次对f() 的调用(下面记录的进一步改进达到约 1.75,tbilly 的约 2.6,Jim Lewis 接受的答案约 5.33)。代码在答案中显示较低。

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


算法说明

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(4 分之三的概率),我们认为 x 位于“可能 x”空间的上四分之三,并从该空间中消除下四分之一 当“可能的 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 结果切换到在下四分之一上磨练,改为在上四分之一上磨练来进行进一步调整,基于此平均解决更快地到达结果桶。从表面上看,这在第三次调用 f() 时似乎很有用,因为上半部分的结果表明立即结果为 3,而下半部分的结果仍然跨越概率点 0.5,因此结果为 1 和 2。当我尝试它时,结果实际上更糟。需要进行更复杂的调整才能看到实际的好处,我最终为 g() 的第二次到第十一次调用编写了下截止与上截止的蛮力比较。我发现的最佳结果是平均约 1.75,这是由于对 g() 的第 1 次、第 2 次、第 5 次和第 8 次调用寻求低(即设置low = cutoff)。

【讨论】:

【参考方案2】:

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

/*
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;

【讨论】:

【参考方案3】:

这很像蒙蒂霍尔悖论。

一般。

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

【讨论】:

【参考方案4】:

假设

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

【讨论】:

我认为这行不通。它假定 f(x)+f(x) 的四个可能值的概率相同。实际上,0,0 的可能性远小于 1,1 看来你自己搞糊涂了:-)。这些函数并不是真正的 f(x) 和 g(x)……它们没有输入,只是 f() 和 g()。因此,没有 f(x+1)。至于 f() + f()... 有 1/16 的机会是 0、6/16 的 1 和 9/16 的 2。你的 g() 函数在测试中“切换”为 1,所以也会这两个结果有 6/16 对 10/16 的机会(需要同样的可能性)。【参考方案5】:

由于 f() 的每次返回都代表 3/4 的机会为 TRUE,因此通过一些代数我们可以适当地平衡赔率。我们想要的是另一个函数 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);

【讨论】:

我认为这暗示你只能使用f(x)作为随机生成器,而不能使用任何其他rand() 啊,有道理。谢谢!【参考方案6】:

您的解决方案是正确的,但效率较低且逻辑重复较多。这是相同算法的 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。

如果你愿意,你可以重复这个想法,逐个生成无穷无尽的比特流。事实上,这可以证明是生成这种流的最有效方式,并且是信息论中思想的来源。

【讨论】:

哎呀,你是对的。固定的。它的工作方式是,如果我们在一个区间内没有停下来,我们就会在我们的包上统一选择一个从 0 到 1 的数字。实际上,一旦我们知道 0.5 的哪一边,我们就会停下来号码将亮起。我会尝试添加解释。 f() 的熵是lg(4)/4+lg(4/3)*3/4 ≈ 0.81。因此,1.23f() 的调用似乎就足够了?【参考方案7】:

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

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.:您的定义目前没有提到“随机”,但可能是假设的。请参阅我的其他答案。

【讨论】:

我投了这个票,然后取消了,因为我以为我看到了一个错误,但现在不能再投了。 :-( 无论如何,+1!当你编辑时,我相信我可以再次投票。也许再解释一下答案?:) @Steven,我看到你玩弄我的名声 :) 您可以通过区分 0、0、1 和 1、0、0.... 来处理 10 个未处理案例中的另外 6 个。【参考方案8】:

如前所述,您对概率的定义不是很好。通常这意味着不仅概率很好,而且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)。但结果不会很随机。

【讨论】:

我不认为这是作弊——你给了面试官他们要求的东西——话虽如此,你的函数最终可能会返回 (0) 反转为 (1), (1) => (1), (1) 反转为 (0) , (1) => (1), 3 个 1 和 1 0。为什么不只计算 f() 一次(说你已经使用它)然后只每次调用 g() 时翻转结果。【参考方案9】:
Given a function f(x) that 1/4 times returns 0, 3/4 times returns 1

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

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

应该足够了。

【讨论】:

我已经多次看到这个问题的变体,以至于我已经知道答案,甚至不必计算概率。因此,在面试问题的背景下,我认为“独立、相同分布的试验”是正确的假设。当然,有些面试官很刻薄,所以最好知道另一个需要准备的“技巧问题”变体。【参考方案10】:

如果连续调用 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)。

【讨论】:

嗯,贝叶斯定理的有趣用法,其中循环引入了归一化…… 谢谢,这与“用有偏见的硬币制作公平的硬币”经典问题***.com/questions/5429045/… 中的答案相同

以上是关于面试题:关于概率的主要内容,如果未能解决你的问题,请参考以下文章

智邮普创c语言面试题 ---- 字母概率

智邮普创c语言面试题 ---- 字母概率

牛客网面试——大数据方面/计算机基础/概率题

Mysql面试题

关于mysql面试题

转:浅谈洗牌算法(面试题)