阵列保持不变的概率是多少?

时间:2012-08-08 19:55:49

标签: algorithm math probability

微软采访中已经提出了这个问题。非常好奇地知道为什么这些人会对概率提出如此奇怪的问题?

给定rand(N),一个随机生成器,它生成从0到N-1的随机数。

int A[N]; // An array of size N
for(i = 0; i < N; i++)
{
    int m = rand(N);
    int n = rand(N);
    swap(A[m],A[n]);
}

编辑:请注意种子未修复。

阵列A保持不变的概率是多少? 假设数组包含唯一元素。

13 个答案:

答案 0 :(得分:21)

  

非常好奇地知道为什么这些人会对概率提出如此奇怪的问题?

这样的问题被问到,因为它们允许访调员深入了解受访者的

  • 能力读取代码(非常简单的代码,但至少有些东西)
  • 分析算法以识别执行路径的能力
  • 应用逻辑以找到可能的结果和边缘情况的技能
  • 解决问题时的推理和解决问题的技巧
  • 沟通和工作技巧 - 他们是否提出问题,或根据手头的信息孤立地工作

......等等。有一个问题暴露受访者的这些属性的关键是要有一段看似简单的代码。这会破坏非编码器卡住的冒名顶替者;傲慢的跳到了错误的结论;懒惰或低于标准的计算机科学家找到一个简单的解决方案并停止寻找。通常,正如他们所说,不是你得到正确的答案,而是你是否对你的思维过程留下了深刻的印象。


我也会尝试回答这个问题。在一次采访中,我会解释自己,而不是提供一个单行的书面答案 - 这是因为即使我的“回答”是错误的,我也能够表现出逻辑思维。

时,A将保持不变 - 即位于相同位置的元素
    在每次迭代中
  • m == n(以便每个元素只与自身交换);或
  • 交换的任何元素都会交换回原来的位置

第一种情况是duedl0r给出的'简单'情况,即数组未被改变的情况。这可能就是答案,因为

  

阵列A保持不变的概率是多少?

如果数组在i = 1处更改然后在i = 2处返回,则它处于原始状态但不会“保持不变” - 它已更改,然后又更改回来。这可能是一个聪明的技术性。

然后考虑元素交换和交换的可能性 - 我认为在面试中计算是我的头脑。显而易见的考虑是,这不需要是一个改变 - 改变回交换,可以很容易地在三个元素之间交换,交换1和2,然后是2和3,1和3,最后是2和3。继续,可能会有4个,5个或更多像这样“循环”的项目进行掉期。

事实上,考虑到 更改的情况,可能更容易考虑数组未更改的情况。考虑是否可以将此问题映射到Pascal's triangle等已知结构。


这是一个难题。我同意在面试中难以解决,但这并不意味着在面试中要求太难。可怜的候选人没有答案,普通候选人会猜出明显的答案,而优秀的候选人会解释为什么问题难以回答。

我认为这是一个'开放式'问题,让面试官能够洞察候选人。出于这个原因,尽管在面试过程中难以解决,但在面试中要问一个好的问题。提问不仅仅是检查答案是对还是错。

答案 1 :(得分:10)

下面是C代码,用于计算rand可以产生的2N元组索引的值的数量并计算概率。从N = 0开始,它显示计数1,1,8,135,4480,189125和12450816,概率为1,1,.5,.185185,.0683594,.0193664和.00571983。计数不会显示在Encyclopedia of Integer Sequences中,因此我的程序有错误或者这是一个非常模糊的问题。如果是这样,问题不是由求职者解决,而是揭露他们的一些思维过程以及他们如何应对挫折。我不认为这是一个很好的面试问题。

#include <inttypes.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>


#define swap(a, b)  do { int t = (a); (a) = (b); (b) = t; } while (0)


static uint64_t count(int n)
{
    // Initialize count of how many times the original order is the result.
    uint64_t c = 0;

    // Allocate space for selectors and initialize them to zero.
    int *r = calloc(2*n, sizeof *r);

    // Allocate space for array to be swapped.
    int *A = malloc(n * sizeof *A);

    if (!A || !r)
    {
        fprintf(stderr, "Out of memory.\n");
        exit(EXIT_FAILURE);
    }

    // Iterate through all values of selectors.
    while (1)
    {
        // Initialize A to show original order.
        for (int i = 0; i < n; ++i)
            A[i] = i;

        // Test current selector values by executing the swap sequence.
        for (int i = 0; i < 2*n; i += 2)
        {
            int m = r[i+0];
            int n = r[i+1];
            swap(A[m], A[n]);
        }

        // If array is in original order, increment counter.
        ++c;    // Assume all elements are in place.
        for (int i = 0; i < n; ++i)
            if (A[i] != i)
            {
                // If any element is out of place, cancel assumption and exit.
                --c;
                break;
            }

        // Increment the selectors, odometer style.
        int i;
        for (i = 0; i < 2*n; ++i)
            // Stop when a selector increases without wrapping.
            if (++r[i] < n)
                break;
            else
                // Wrap this selector to zero and continue.
                r[i] = 0;

        // Exit the routine when the last selector wraps.
        if (2*n <= i)
        {
            free(A);
            free(r);
            return c;
        }
    }
}


int main(void)
{
    for (int n = 0; n < 7; ++n)
    {
        uint64_t c = count(n);
        printf("N = %d:  %" PRId64 " times, %g probabilty.\n",
            n, c, c/pow(n, 2*n));
    }

    return 0;
}

答案 2 :(得分:4)

很容易观察到边界1 / n n &lt; = p&lt; = 1 / n。

这是显示反指数上限的不完整概念。

您从{1,2,..,n}中抽取数字2次。如果它们中的任何一个是唯一的(恰好发生一次),那么数组肯定会被更改,因为元素已经消失并且无法在其原始位置返回。

固定数字唯一的概率是2n * 1 / n *(1-1 / n)^(2n-1)= 2 *(1-1 / n)^(2n-1),这是一个非常规的2 / e 2 ,远离0. [2n因为你选择了哪个尝试得到它,1 / n你得到它的尝试,(1-1 / n)^(2n -1)你没有在其他尝试中得到它]

如果事件是独立的,那么你将获得所有数字都不是唯一的机会(2 / e 2 )^ n,这意味着p <= O((2 / E 2 )^ N)。不幸的是,他们不是独立的。我觉得可以通过更复杂的分析来展示界限。关键字是&#34;球和垃圾箱问题&#34;。

答案 3 :(得分:3)

一个简单的解决方案是

  

p> = 1 / N N

由于数组保持不变的一种可能方式是每次迭代都m = nm等于n,可能性为1 / N

肯定比那更高。问题是多少..

第二个想法:人们也可以争辩说,如果你随机地改变一个数组,每个排列都有相同的概率。由于存在n!个排列,因此只获得一个(我们在开头的那个)的概率是

  

p = 1 / N!

比上一个结果好一点。

如上所述,该算法存在偏差。这意味着并非每个排列具有相同的概率。所以1 / N!并不完全正确。你必须弄清楚排列的分布情况。

答案 4 :(得分:3)

仅供参考,不确定上述(1 / n ^ 2)的界限是否成立:

N=5 -> 0.019648 < 1/25
N=6 -> 0.005716 < 1/36

采样代码:

import random

def sample(times,n):
    count = 0;
    for i in range(times):
        count += p(n)
    return count*1.0/times;

def p(n):
    perm = range(n);
    for i in range(n):
        a = random.randrange(n)
        b = random.randrange(n)

        perm[a],perm[b]=perm[b],perm[a];


    return perm==range(n)

print sample(500000,5)

答案 5 :(得分:3)

每个人都假设A[i] == i,这是没有明确的 说明。我也要做出这个假设,但请注意概率 取决于内容。例如,如果A[i]=0,则概率= 1 全部N。

这是怎么做的。设P(n,i)为结果数组的概率 与原始数组中的i个转置完全不同。

我们想知道P(n,0)。的确如此:

P(n,0) = 
1/n * P(n-1,0) + 1/n^2 * P(n-1,1) = 
1/n * P(n-1,0) + 1/n^2 * (1-1/(n-1)) * P(n-2,0)

说明:我们可以通过两种方式获得原始数组,或者通过在已经很好的数组中进行“中性”转置,或者通过恢复唯一的“坏”转置。为了得到一个只有一个“坏”换位的数组,我们可以得到一个带有0个不良转置的数组,并进行一个非中性的转置。

编辑:-2代替P(n-1,0)中的-1

答案 6 :(得分:1)

这不是一个完整的解决方案,但它至少是这样的。

采取一组无效的掉期交易。我们知道,它的交换最终会形成一堆不同大小的循环,使用总共n个交换。 (出于此目的,无效的交换可被视为大小为1的循环)

也许我们可以

1)根据循环的大小,将它们分成几组 2)计算获得每个小组的方式数。

(主要问题是有不同组的 ton ,但如果不考虑不同的分组,我不确定你是如何实际计算的。)

答案 7 :(得分:1)

有趣的问题。

我认为答案是1 / N,但我没有任何证据。当我找到证据时,我会编辑我的答案。

到目前为止我得到了什么:

如果m == n,则不会更改数组。 得到m == n的概率是1 / N,因为有N ^ 2个选项,并且只有N是合适的((i,i)每0 <= i <= N-1)。

因此,我们得到N / N ^ 2 = 1 / N.

表示Pk在交换k次迭代后,大小为N的数组保持不变的概率。

P1 = 1 / N. (正如我们在下面看到的那样)

P2 =(1 / N) P1 +(N-1 / N)(2 / N ^ 2)= 1 / N ^ 2 + 2(N-1)/ N ^ 3

Explanation for P2:
We want to calculate the probability that after 2 iterations, the array with 
N elements won't change. We have 2 options : 
- in the 2 iteration we got m == n (Probability of 1/N)
- in the 2 iteration we got m != n (Probability of N-1/N)

If m == n, we need that the array will remain after the 1 iteration = P1.
If m != n, we need that in the 1 iteration to choose the same n and m 
(order is not important). So we get 2/N^2.
Because those events are independent we get - P2 = (1/N)*P1 + (N-1/N)*(2/N^2).

Pk =(1 / N)* Pk-1 +(N-1 / N)* X. (第一个是m == n,第二个是m!= n)

我必须更多地考虑X等于什么。 (X只是真实公式的替代品,而不是常数或其他任何东西)

Example for N = 2.
All possible swaps:

(1 1 | 1 1),(1 1 | 1 2),(1 1 | 2 1),(1 1 | 2 2),(1 2 | 1 1),(1 2 | 1 2)
(1 2 | 2 1),(1 2 | 2 2),(2 1 | 1 1),(2 1 | 1 2),(2 1 | 2 1),(2 1 | 2 2)
(2 2 | 1 1),(2 2 | 1 2),(2 2 | 2 1),(2 1 | 1 1).

Total = 16. Exactly 8 of them remain the array the same.
Thus, for N = 2, the answer is 1/2.

编辑: 我想介绍另一种方法:

我们可以将互换分为三类:建设性互换,破坏性互换和无害互换。

构造性交换被定义为交换,导致至少一个元素移动到正确的位置。

破坏性交换被定义为交换,导致至少一个元素从其正确位置移动。

无害交换被定义为不属于其他组的交换。

很容易看出这是所有可能交换的分区。 (intersection = empty set)。

现在我想证明这个说法:

    The array will remain the same if and only if 
the number of Destructive swap == Constructive swap in the iterations.

如果某人有反例,请将其作为评论写下来。

如果这个说法是正确的,我们可以采取所有组合并总结它们 - 0无害互换,1无害互换,...​​,N无害互换。

对于每个可能的k无害交换,我们检查N-k是否是偶数,如果不是,我们跳过。如果是,我们采用(N-k)/ 2表示破坏性,而(N-k)表示建设性。只看所有可能性。

答案 8 :(得分:1)

我会将问题建模为一个多图,其中节点是数组的元素,而交换是在它们之间添加一个非定向(!)连接。然后以某种方式寻找循环(所有节点都是循环的一部分=&gt;原始)

真的需要重新开始工作! :(

答案 9 :(得分:1)

好吧,从数学的角度来看:

每次都在同一个地方交换数组元素,然后Rand(N)函数必须为int m和int n生成两次相同的数字。因此Rand(N)函数两次生成相同数字的概率是1 / N. 我们在for循环中调用了N次Rand(N),因此我们的概率为1 /(N ^ 2)

答案 10 :(得分:1)

C#中的朴素实现。 我们的想法是创建初始数组的所有可能的排列并枚举它们。 然后我们建立一个可能的状态变化矩阵。将矩阵乘以N次,我们将得到矩阵,显示在N个步骤中从排列#i到排列#j的多少种方式。 Elemet [0,0]将显示将导致相同初始状态的方式。第0行的元素总和将显示不同方式的总数。通过将前者除以后者,我们得到概率。

实际上,排列的总数是N ^(2N)。

Output:
For N=1 probability is 1 (1 / 1)
For N=2 probability is 0.5 (8 / 16)
For N=3 probability is 0.1851851851851851851851851852 (135 / 729)
For N=4 probability is 0.068359375 (4480 / 65536)
For N=5 probability is 0.0193664 (189125 / 9765625)
For N=6 probability is 0.0057198259072973293366526105 (12450816 / 2176782336)

class Program
{
    static void Main(string[] args)
    {
        for (int i = 1; i < 7; i++)
        {
            MainClass mc = new MainClass(i);
            mc.Run();
        }
    }
}

class MainClass
{
    int N;
    int M;

    List<int> comb;
    List<int> lastItemIdx;
    public List<List<int>> combinations;
    int[,] matrix;

    public MainClass(int n)
    {
        N = n;

        comb = new List<int>();
        lastItemIdx = new List<int>();
        for (int i = 0; i < n; i++)
        {
            comb.Add(-1);
            lastItemIdx.Add(-1);
        }

        combinations = new List<List<int>>();
    }

    public void Run()
    {
        GenerateAllCombinations();
        GenerateMatrix();
        int[,] m2 = matrix;
        for (int i = 0; i < N - 1; i++)
        {
            m2 = Multiply(m2, matrix);
        }

        decimal same = m2[0, 0];
        decimal total = 0;
        for (int i = 0; i < M; i++)
        {
            total += m2[0, i];
        }

        Console.WriteLine("For N={0} probability is {1} ({2} / {3})", N, same / total, same, total);
    }

    private int[,] Multiply(int[,] m2, int[,] m1)
    {
        int[,] ret = new int[M, M];
        for (int ii = 0; ii < M; ii++)
        {
            for (int jj = 0; jj < M; jj++)
            {
                int sum = 0;

                for (int k = 0; k < M; k++)
                {
                    sum += m2[ii, k] * m1[k, jj];
                }

                ret[ii, jj] = sum;
            }
        }

        return ret;
    }

    private void GenerateMatrix()
    {
        M = combinations.Count;
        matrix = new int[M, M];

        for (int i = 0; i < M; i++)
        {
            matrix[i, i] = N;
            for (int j = i + 1; j < M; j++)
            {
                if (2 == Difference(i, j))
                {
                    matrix[i, j] = 2;
                    matrix[j, i] = 2;
                }
                else
                {
                    matrix[i, j] = 0;
                }
            }
        }
    }

    private int Difference(int x, int y)
    {
        int ret = 0;
        for (int i = 0; i < N; i++)
        {
            if (combinations[x][i] != combinations[y][i])
            {
                ret++;
            }

            if (ret > 2)
            {
                return int.MaxValue;
            }
        }

        return ret;
    }

    private void GenerateAllCombinations()
    {
        int placeAt = 0;
        bool doRun = true;
        while (doRun)
        {
            doRun = false;
            bool created = false;

            for (int i = placeAt; i < N; i++)
            {
                for (int j = lastItemIdx[i] + 1; j < N; j++)
                {
                    lastItemIdx[i] = j; // remember the test

                    if (comb.Contains(j))
                    {
                        continue; // tail items should be nulled && their lastItemIdx set to -1
                    }

                    // success
                    placeAt = i;
                    comb[i] = j;
                    created = true;
                    break;
                }

                if (comb[i] == -1)
                {
                    created = false;
                    break;
                }
            }

            if (created)
            {
                combinations.Add(new List<int>(comb));
            }

            // rollback 
            bool canGenerate = false;
            for (int k = placeAt + 1; k < N; k++)
            {
                lastItemIdx[k] = -1;
            }

            for (int k = placeAt; k >= 0; k--)
            {
                placeAt = k;
                comb[k] = -1;

                if (lastItemIdx[k] == N - 1)
                {
                    lastItemIdx[k] = -1;
                    continue;
                }

                canGenerate = true;
                break;
            }

            doRun = canGenerate;
        }
    }
}

答案 11 :(得分:0)

每次迭代时m == n的概率,然后进行N次。 P(m == n)= 1 / N.所以我认为对于那种情况P = 1 /(n ^ 2)。但是你必须考虑换回的值。所以我认为答案是(文本编辑得到我)1 / N ^ N。

答案 12 :(得分:0)

问题:阵列A保持不变的概率是多少? 条件:假设数组包含唯一元素。

尝试用Java解决方案。

在原始int数组上发生随机交换。在java方法中,参数总是按值传递,所以在swap方法中发生的事情并不重要,因为数组的[m]和[n]元素(来自下面的代码交换(a [m],a [n]))是通过不完整的数组。

答案是数组将保持不变。尽管有上述条件。见下面的java代码示例:

import java.util.Random;

public class ArrayTrick {

    int a[] = new int[10];
    Random random = new Random();

    public void swap(int i, int j) {
        int temp = i;
        i = j;
        j = temp;
    }

    public void fillArray() {
        System.out.println("Filling array: ");
        for (int index = 0; index < a.length; index++) {
            a[index] = random.nextInt(a.length);
        }
    }

    public void swapArray() {
        System.out.println("Swapping array: ");
        for (int index = 0; index < a.length; index++) {
            int m = random.nextInt(a.length);
            int n = random.nextInt(a.length);
            swap(a[m], a[n]);
        }
    }

    public void printArray() {
        System.out.println("Printing array: ");
        for (int index = 0; index < a.length; index++) {
            System.out.print(" " + a[index]);
        }
        System.out.println();
    }

    public static void main(String[] args) {
        ArrayTrick at = new ArrayTrick();

        at.fillArray();
        at.printArray();
        at.swapArray();
        at.printArray();
    }
}

示例输出:

填充数组: 印刷阵列:  3 1 1 4 9 7 9 5 9 5 交换数组: 印刷阵列:  3 1 1 4 9 7 9 5 9 5