处理N中的M次出现

时间:2010-10-18 21:07:49

标签: c++ arrays algorithm

问题我在求职面试中得到了。我很接近解决方案,但不幸的是没有解决它。

假设我们的序列包含 N 类型long的数字。我们肯定知道,在这个序列中,每个数字确实出现 n 次,除了一个完全出现 m <的数字/ strong>次( 0 &lt; m &lt; n )。我们如何通过 O(N)操作和 O(1)附加内存找到该号码?

对于最简单的情况( n = 2 m = 1 )我们应该做的只是按顺序对每个数字执行累积xor。结果将等于所需的数字。但我在尝试处理任意 m n 时遇到困难。

我很欣赏实际的C ++解决方案。


编辑:我们知道 m n 的实际值。

示例。我们知道 n = 3且 m = 2 。序列( N = 8 )是

5 11 5 2 11 5 2 11

在这种特殊情况下,正确的答案是 2 ,因为它只发生了两次。

8 个答案:

答案 0 :(得分:30)

对于计算得到的每个总和,你为每个位做64个求和,这个计算为每个应该在结果中设置的位返回m,对于不应该设置的每个位都返回0

示例:
n = 3,m = 2. list = [5 11 5 2 11 5 2 11]

              5  11   5   2  11   5   2  11
sum of bit 0: 1 + 1 + 1 + 0 + 1 + 1 + 0 + 1 = 6   6 % 3 = 0
sum of bit 1: 0 + 1 + 0 + 1 + 1 + 0 + 1 + 1 = 5   5 % 3 = 2
sum of bit 2: 1 + 0 + 1 + 0 + 0 + 1 + 0 + 0 = 3   3 % 3 = 0
sum of bit 3: 0 + 1 + 0 + 0 + 1 + 0 + 0 + 1 = 3   3 % 3 = 0

因此只设置了第1位,这意味着结果为2.

优化实施:
(对实际问题也有用的技巧和注意事项)
值得注意的是,当迭代一个数组时,执行速度在某种程度上会受到内存访问的限制,如果需要对每个元素执行多个操作,通常最快在一个元素上一次执行它们,因此处理器只需要从内存中加载一次元素。 Interesting blog post on memory and cache.

可以在一个整数中对多个位求和,而不是应用64个不同的位掩码来获取它自己的位,例如,可以使用4个位掩码,每个位掩码提取16位,每个位之间有3位空间,如只要不发生溢出,正常的加法运算就像处理16个4位整数一样工作,因此这种方法适用于15个数字。在以这种方式处理了15个数之后,必须将结果添加到能够容纳更大整数的存储器中(可以是8个64位整数,每个整数保持8个8位整数,它们当然必须被清空为更大的整数等。 )。
结果是,不是每个值进行64位掩码,63位移位和64次加法,只需要进行4位掩码,3位移位和4次加法,再加上每15个值8位掩码,4位移位和8位加法,加上每255个值16位掩码,8位移位和16位加法等。

<强>可视化:
(使用16位整数求和4x4位整数)

1000 1000 1000 1000 +
1000 0000 0000 1000 +
0000 0000 0000 1000 +
1000 1000 0000 0000 +
1000 0000 1000 0000 +
0000 0000 1000 1000 =
0010 0100 1100 0010

无论您认为这是4列4位整数还是1列16位整数,结果都是一样的,只要4位整数不溢出,这只是真的。

答案 1 :(得分:9)

编辑)好的,这种方法并不像我最初想的那样健全。电子商务的解决方案更简单,适用于n = 4,m = 2等情况。

我们可以将xor方法推广到任意 m n 。我们首先需要选择一个基数 b ,以便 gcd(n,b)= b ,并且 gcd(m,b)&lt; B'/ strong>即可。由于奇数 n /甚至 m 对满足基数2,标准二进制xor适用于这些对。

首先,我们将(a ^^ n)定义为 na (a ^ a ^ ... ^ a) ,基数 b 的广义xor。例如,使用标准二进制xor, a ^^ 2 = 0

我们需要定义我们的广义xor。我们想要的属性与添加(交换性,关联性)基本相同,我们需要 a ^^ b = 0 。对于基本 b 表示中的每个数字,显而易见的解决方案是(x ^ y)=(x + y)%b (说服自己这是有效的,并且与二进制xor为基数2)。然后,我们只是将其应用于序列中的所有数字,最后得到 result = s ^^(m%b),其中s是特殊数字。
最后,我们需要将'xor'ed base b 数字还原为预期数字。我们可以简单地为 i = 0..b-1 计算 i ^^(m%b),然后在 s <中查找我们的值/ strong>对于结果中的每个数字。

该算法为O(N)。对于列表中的每个数字,我们有一定数量的操作要做,因为我们最多有64位数。对于大b,最后回复最坏的是O(N)。我们可以通过计算每个数字的所有 i i ^^(m%b)来在恒定空间中完成最后一步(同样,我们有一个恒定的位数)


实施例

n = 3, m = 2. list = [5 11 5 2 11 5 2 11]

首先我们选择基数 b 。显然我们必须选择基数3。

xor表供参考:

  0|1|2
0|0|1|2
1|1|2|0
2|2|0|1

计算:

  5     11      5      2     11      5      2     11
0^0=0. 0^1=1. 1^0=1. 1^0=1. 1^1=2. 2^0=2. 2^0=2. 2^1=0.
0^1=1. 1^0=1. 1^1=2. 2^0=2. 2^0=2. 2^1=0. 0^0=0. 0^0=0.
0^2=2. 2^2=1. 1^2=0. 0^2=2. 2^2=1. 1^2=0. 0^2=2. 2^2=1.

m % b = 2.

因此我们有s ^^ 2 = [001]。我们为每个数字i生成一个i ^^ 2表,然后进行反向查找。

   i | 0 | 1 | 2 |
i^^2 | 0 | 2 | 1 |

0 -> 0
0 -> 0
1 -> 2

我们最后将结果转换回二进制/十进制。 [002] = 2。

答案 2 :(得分:3)

您最简单的情况可以更一般,您可以使用您描述的奇数 m 和偶数 n 的相同技术。

答案 3 :(得分:3)

这是一个与eBusiness具有相同运行时间的解决方案(我认为实际上是O(N log N)),但真正使用O(1)字的内存。它假设m不是n的倍数。它还假设有两个辅助函数,用于计算严格在其参数之上和之下的元素数。

int divider = 0;

for (int i = 63; i >= 0; i--) {
  divider |= 1 << i;
  int a = countAbove(divider);
  int b = countBelow(divider);
  if (a % n == 0 && b % n == 0) return divider;
  else if (a % n == 0) divider ^= 1 << i;
}

答案 4 :(得分:2)

  • 如果在从0到(N / n)+ 1的整数集上有一对一的哈希值,那么你可以通过N次迭代+ N次内存使用的N / n次迭代来解决它。但是,没有一对一的映射

  • 如果对内存没有约束,那么它必须是常量你可以定义一个longs域大小的数组,然后你可以在2N中解决问题,并且内存使用量很大。对于N中的每个x,您只需添加到BIGARRY [x]然后通过BIGARRY循环寻找m。它的可怕性和不可实现但符合要求,大多数面试问题都是以任何方式进行的实验。

答案 5 :(得分:0)

如果列表已经排序,那么这变得非常简单,因为您只需要依次检查每个批次以查看它是否为长度m。

如果列表没有排序,那么我认为O(1)额外的内存是不可能的。

答案 6 :(得分:0)

我相信你不能只使用额外的O(1)空间。

这是我的理由:给你:

  • 名词
  • x_1 x_2 .. x_N

由于x_i值之间存在重复,因此我们将U定义为唯一值集。 U中的所有元素都出现n次,其中一个在x_i系列中出现m次。让我们将不常出现的元素标记为u_0,将U_1标记为U - {u_0}。

设S是所有x_i的总和。 S可以写成:

sum(x_i) = n * sum(U_1) + m * u_0 = n * sum(U) + (m - n) * u_0

解决这个问题等同于查找系列中唯一元素的总和,而不能在O(1)额外空间中执行此操作,因为您需要一个数组或带有链接元素的哈希表 - 空间实际上是O(N)

答案 7 :(得分:0)

解决方案有点像找到第k阶统计量的过程

by dividing the sequence into 2 sub-seqs
(calculate the size of sub-seqs during the procedure)
while (sizeof(sub-seq) mod n != 0)
  do the same porcess on this sub-seq(dividing)

O(N)操作,例如找到第k阶统计量。