解决是 - 否测试

时间:2014-06-27 10:18:52

标签: algorithm

N是或否问题的测试。您可以编写测试,教授会告诉您有多少答案是正确的。通过测试的最快方法是什么?即用最少的试验次数给出所有问题的正确答案。

UPD N+1试验的解决方案很明显。在每次试验中,我们都会对一个问题有正确答案。它是1位信息。但教授每次给我们一个从0到N的数字,它是log 2 (N + 1)位的信息。这就是最佳解决方案具有O(N / log(N))复杂性的原因。 我正在寻找具有亚线性最差时间复杂度的任何解决方案。

6 个答案:

答案 0 :(得分:5)

对N + 1解决方案的明显改进:

从所有Y答案开始。

然后我们确切知道有多少是/否。

p为任何给定位置的肯定概率。 p >= 1/2不失一般性。

然后,我将平均显示2 - p^2次尝试的两个第一个答案。

我改变了我对前两个问题的答案。 至少p^2我会知道他们两个人的确切答案。如果没有 - 那么我至少知道其中一个是Y而另一个是N,我需要再问一个问题。

所以在p = 1/2的最糟糕情况下,我需要1 + N * 7/8

答案 1 :(得分:3)

免责声明:我不知道这是否是最快的方法。我敢肯定,在特定的情况下你可以减少试验,但这可能是一个严格的上限(最糟糕的情况)。

根据您的喜好填写第一轮试用版,只需记住您的选择。如果您愿意,可以为所有人选择“否”。

在下一个试验中,仅更改第一个答案(在示例后面:选择是)。根据答案的变化,您将知道第一个问题的正确答案(如果结果增加,如果没有,则给出正确答案,不正确答案)。

现在只改变第二个,依此类推。

你需要N + 1条小径。

答案 2 :(得分:3)

这是另一种方法(分而治之)。为简单起见,我将引用N的特定值,但概括很简单。

N=10让我们假设在第一轮(trial=1)我们正确回答了5个问题。这是最有可能的结果,也是揭示信息量较少的结果。

然后以下逻辑允许我们不检查每个数字:

  • 将答案列表分为两组1...56...10。鉴于我们有5个正确的答案,每组的可能正确答案是

    (0,5)
    (1,4)
    (2,3)
    (3,2)
    (4,1)
    (5,0)
    

现在,对于下一个试验,请翻阅答案1...5。然后上述状态改变如下:

    (0,5)--> (5,5)   -- 10 correct
    (1,4)--> (4,4)   -- 8 correct
    (2,3)--> (3,3)   -- 6 correct
    (3,2)--> (2,2)   -- 4 correct
    (4,1)--> (1,1)   -- 2 correct
    (5,0)--> (0,0)   -- 0 correct

如果老师说100我们已经完成,或者我们需要再分别进行一次试验。在任何情况下,根据老师说的数字,我们可以知道每个区间有多少正确的答案。然后我们可以决定。

  • 2正确:我们翻回(回溯)前5个答案,我们知道答案1...56...10分别有(4,1)正确。所以我们也翻转6...10并得到8正确,见下文。
  • 4正确:正如2正确一样,我们会翻回前5个答案并翻转6...10并转到6正确,请参阅下文。
  • 6正确:我们需要进一步划分并迭代。对于1...56..10,我们最多需要3个步骤,因此共有8个步骤,包括前两个步骤。
  • 8正确:我们再次应用二进制搜索。我们将两个初始集合中的每一个(例如1...5划分为1...34...5)并找出错误的答案。如果它在4...5,我们需要两个步骤,否则我们需要三个步骤。因此,共有8个步骤。

答案 3 :(得分:3)

在Carsten评论的article中,Erdös和Rényi展示了如何将问题表述为找到最小数量的测试序列的示例,这些测试序列可以共同为未知序列生成唯一的哈希值。由于他们的例子显示了一个五位数的序列,用四次测试求解,我试图为长度为六和七的序列提出一个亚线性数量的测试。

看看Erdös和Rényi的例子,并受到Ioannis提到的“分而治之”的启发,我想也许测试序列有点分裂然后细分序列。花了几次尝试才得到序列长度为7的工作测试。

也许考虑你要求的算法的一种方法可能是推广/自动生成这些测试序列的方法。

下面的JavaScript程序将测试序列与给定长度的所有序列进行比较,并对重合的数字进行散列。如果两个不同的序列生成相同的散列,则程序会通知找到匹配,这意味着这种组合的测试不起作用。如果没有找到匹配,则表示哈希值是唯一的,测试应该有效。

// http://resnet.uoregon.edu/~gurney_j/jmpc/bitwise.html
function setBits(i)
{
     i = i - ((i >> 1) & 0x55555555);
     i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
     return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
}

// http://stackoverflow.com/questions/10073699/pad-a-number-with-leading-zeros-in-javascript
function pad(n, width, z) {
  z = z || '0';
  n = n + '';
  return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
}

function numCoincidences(a,b){
    var n = 0
    for (var i=0; i<a.length; i++){
        if (a.charAt(i) == b.charAt(i)){
            n ++
        }
    }
    return n
}

var sequenceLength = 6

var tests = [
        "111111",
        "111000",
        "010010",
        "011001",
        "000100"
    ]

/***
var sequenceLength = 7

var tests = [
    "1111111",
    "1111000",
    "0100100",
    "0110010",
    "0110001",
    "0001000"
]
***/

var hash = {}

console.log("       " + tests.join(" "))

for (var i=0; i<1<<sequenceLength; i++){
    if (setBits(i) < Math.floor(sequenceLength / 2)){
      var tmp = pad(i.toString(2),sequenceLength)
      var h = ""
      for (var j in tests){
        h += numCoincidences(tests[j],tmp)
      }
      console.log(tmp + "   " + h.split("").join("      "))
      if (hash[h]){
          console.log("found match")
      } else {
          hash[h] = true
      }

    }
}

console.log("done")

输出:

"       111111 111000 010010 011001 000100" <-- test sequences
"000000   0      3      4      3      5"
"000001   1      2      3      4      4"    <-- sequences to match, followed by
"000010   1      2      5      2      4"             the number of coincidences 
"000011   2      1      4      3      3" 
"000100   1      2      3      2      6" 
"000101   2      1      2      3      5" 
"000110   2      1      4      1      5" 
"000111   3      0      3      2      4" 
"001000   1      4      3      4      4" 
"001001   2      3      2      5      3" 
"001010   2      3      4      3      3" 
"001011   3      2      3      4      2" 
"001100   2      3      2      3      5" 
"001101   3      2      1      4      4" 
"001110   3      2      3      2      4" 
"010000   1      4      5      4      4" 
"010001   2      3      4      5      3" 
"010010   2      3      6      3      3" 
"010011   3      2      5      4      2" 
"010100   2      3      4      3      5" 
"010101   3      2      3      4      4" 
"010110   3      2      5      2      4" 
"011000   2      5      4      5      3" 
"011001   3      4      3      6      2" 
"011010   3      4      5      4      2" 
"011100   3      4      3      4      4" 
"100000   1      4      3      2      4" 
"100001   2      3      2      3      3" 
"100010   2      3      4      1      3" 
"100011   3      2      3      2      2" 
"100100   2      3      2      1      5" 
"100101   3      2      1      2      4" 
"100110   3      2      3      0      4" 
"101000   2      5      2      3      3" 
"101001   3      4      1      4      2" 
"101010   3      4      3      2      2" 
"101100   3      4      1      2      4" 
"110000   2      5      4      3      3" 
"110001   3      4      3      4      2" 
"110010   3      4      5      2      2" 
"110100   3      4      3      2      4" 
"111000   3      6      3      4      2" 
"done"

答案 4 :(得分:1)

天真的解决方案将涉及O(N)尝试:从所有答案开始是,然后在每i尝试翻转i答案。如果你的分数增加,请保留;如果没有,请将其翻转。增加i,重复。

更有效的解决方案可能涉及一种非常简单的遗传算法,其中启发式是教授的答案,而变异可能等同于简单地翻转所有答案。这可能接近O(log N)尝试,但当然乘法常数会更大(至少一个数量级,如果我不得不猜),所以它只适用于大N.

答案 5 :(得分:1)

一些简单的O(n)算法的Python代码:

import random

def ask_prof(A, M): return sum(x == y for x, y in zip(M, A)) 

N = 10
A = [ random.randint(0, 1) for x in range(N) ]
K, s = [], 0
for trial in range(N):
    M = K + [ 0 for x in range(N - len(K)) ]
    s1 = ask_prof(A, M)
    M[len(K)] = 1 
    s2 = ask_prof(A, M)
    if s1 < s2: K.append(1)
    else: K.append(0)
print 'answers are', K