关于算法:检查数组B是否是A的排列

Check if array B is a permutation of A

我试图找到解决这个问题的方法,但没能从我的头脑中得到多少。

我们有两个未排序的整数数组a和b。我们必须检查数组b是否是a的排列。如何才能做到这一点?即使是异或数也不会起作用,因为可能有几个具有相同异或值的反例bt不是相互排列的。

解决方案必须是O(n)时间和空间O(1)

欢迎任何帮助!!谢谢。


这个问题是理论上的,但你可以在O(n)时间和O(1)空间中解决。分配一个232个计数器的数组,并将它们全部设置为零。这是O(1)步,因为数组的大小是常量。然后遍历这两个数组。对于数组A,递增与读取的整数对应的计数器。对于数组b,递减它们。如果在数组b的迭代过程中遇到负值,停止——数组不是彼此的排列。否则,在结束时(假设A和B的大小相同,这是一个先决条件),计数器数组都为零,并且这两个数组是相互排列的。

这是O(1)空间和O(n)时间的解决方案。然而,这是不实际的,但很容易作为一个解决面试问题的办法。至少应该这样。

更模糊的解决方案

  • 使用一个不确定的计算模型,通过猜测两个数组的计数不同的元素,然后计算两个数组上该元素的实例,可以在o(1)空间、o(n)时间内检查两个数组是否相互排列。

  • 在随机计算模型中,构造一个随机交换散列函数,计算两个数组的散列值。如果散列值不同,则数组不是彼此的排列。否则他们可能是。重复多次,使错误概率低于所需阈值。也适用于O(1)空间O(N)时间法,但随机。

  • 在并行计算模型中,"n"是输入数组的大小。分配"n"个线程。每根线i=1.n从第一个数组中读取第i个数字;设为x。然后相同的线程计算第一个数组中出现x的次数,然后检查第二个数组中出现的次数是否相同。每个线程都使用O(1)空间和O(n)时间。

  • 将整数数组[a1,…,an]解释为多项式xa1+xa2+…+其中x是一个自由变量,用数值方法检验两个多项式的等价性。对O(1)空间和O(n)时间操作使用浮点运算。由于舍入误差和等效性的数值检查是概率性的,所以不是一种精确的方法。或者,解释整数上模素数的多项式,并执行相同的概率检查。


如果允许我们自由地访问大量素数列表,您可以通过利用素数因子分解的属性来解决这个问题。

对于两个数组,计算每个整数i的质数[i]的积,其中质数[i]是第i个质数。数组乘积的值相等,如果它们是彼此的排列。

主要因子分解有两个原因。

  • 乘法是可传递的,因此计算乘积的操作数顺序是不相关的。(有些人提到,如果对数组进行排序,这个问题将是微不足道的。通过乘法,我们隐式排序。)
  • 素数乘以无损。如果给我们一个数,告诉我们它只是质数的乘积,我们就可以精确地计算出哪些质数被输入它,以及有多少个质数。
  • 例子:

    1
    2
    3
    4
    a = 1,1,3,4
    b = 4,1,3,1
    Product of ith primes in a = 2 * 2 * 5 * 7 = 140
    Product of ith primes in b = 7 * 2 * 5 * 2 = 140

    也就是说,我们可能不允许访问素数列表,但这似乎是一个很好的解决方案,否则,我想我会发布它。


    我很抱歉将此作为答案发布,因为这真的应该是对安蒂的评论。惠玛的回答,但我还没有评论的声誉。

    计数器数组的大小似乎是O(log(n)),因为它取决于输入数组中给定值的实例数。

    例如,让输入数组a都是1,长度为(2^32)+1。这将需要一个33位大小的计数器进行编码(实际上,这将使数组的大小增加一倍,但让我们继续学习理论)。使A的大小加倍(仍为1个值),每个计数器需要65位,依此类推。

    这是一个非常挑剔的论点,但这些面试问题往往是非常挑剔的。


    如果我们不需要在适当的位置对其进行排序,那么下面的方法可能有效:

  • 创建一个hashmap,key作为数组元素,value作为出现次数。(处理同一数字的多次出现)
  • 导线阵列A。
  • 在哈希图中插入数组元素。
  • 接下来,遍历数组B。
  • 搜索hashmap中b的每个元素。如果对应值为1,则删除条目。否则,将值减小1。
  • 如果我们能够处理整个数组B,而此时hashmap是空的,那么就成功了。否则失败。
  • hashmap将使用常量空间,您将只遍历每个数组一次。

    不确定这是不是你要找的。如果我错过了任何关于空间/时间的限制,请告诉我。


    解决方案需要是O(n)时间和空间O(1)。这就省去了排序,而空间O(1)要求是一个提示,提示您可能应该对字符串进行散列并进行比较。

    如果你有权访问一个质数列表,就按照Cheeken的解决方案来做。

    注意:如果面试官说你没有访问主号码列表的权限。然后生成质数并存储它们。这是O(1),因为字母长度是一个常数。

    另外,这是我的另一个想法。为了简单起见,我将把字母表定义为A、B、C、D、E。字母的值定义为:

    1
    2
    a, b, c, d, e
    1, 2, 4, 8, 16

    注意:如果面试官说这是不允许的,那么就为字母表做一个查阅表格,这需要O(1)个空格,因为字母表的大小是一个常量。

    定义可以在字符串中查找不同字母的函数。

    1
    2
    3
    4
    5
    6
    // set bit value of char c in variable i and return result
    distinct(char c, int i) : int

    E.g. distinct('a', 0) returns 1
    E.g. distinct('a', 1) returns 1
    E.g. distinct('b', 1) returns 3

    因此,如果迭代字符串"aab",distinct函数的结果应该是3

    定义一个函数,该函数可以计算字符串中字母的和。

    1
    2
    3
    4
    5
    6
    // return sum of c and i
    sum(char c, int i) : int

    E.g. sum('a', 0) returns 1
    E.g. sum('a', 1) returns 2
    E.g. sum('b', 2) returns 4

    因此,如果迭代字符串"aab",sum函数的结果应该是4

    定义一个可以计算字符串中字母长度的函数。

    1
    2
    3
    4
    // return length of string s
    length(string s) : int

    E.g. length("aab") returns 3

    在两个字符串上运行方法并比较结果需要O(n)个运行时间。存储哈希值需要在空间中使用O(1)。

    1
    2
    3
    4
    5
    6
    7
     e.g.
     distinct of"aab" => 3
     distinct of"aba" => 3
     sum of"aab => 4
     sum of"aba => 4
     length of"aab => 3
     length of"aba => 3

    由于两个字符串的所有值都相等,因此它们必须是彼此的排列。

    编辑:根据注释中指出的给定字母值,解决方案不正确。


    有两个约束:计算O(n),其中n表示A和B以及内存O(1)的总长度。

    如果两个系列a,b是彼此的排列,那么也有一个系列c是由a或b的排列产生的。所以问题是将a和b都排列成系列c_a和c_b并进行比较。

    这样的排列之一就是排序。有几种排序算法可以在适当的位置工作,所以您可以在适当的位置对A和B进行排序。现在,在最佳情况下,平滑排序与O(n)计算和O(1)内存复杂性排序,在最坏情况下与O(n logn)/O(1)排序。

    然后,每个元素的比较发生在o(n)处,但是由于在o符号o(2*n)=o(n)中,使用平滑排序和比较将给您一个o(n)/o(1)检查两个序列是否是彼此的排列。但是,在最坏的情况下,它将是O(n log n)/O(1)


    我只是找到一个反例。因此,下面的假设是错误的。

    我不能证明,但我认为这可能是真的。

    因为数组的所有元素都是整数,假设每个数组有2个元素,我们有

    1
    2
    3
    4
    5
    6
    7
    a1 + a2 = s
    a1 * a2 = m

    b1 + b2 = s
    b1 * b2 = m

    then {a1, a2} == {b1, b2}

    如果这是真的,那么数组有n个元素也是真的。

    所以我们比较每个数组的和和和和积,如果它们相等,一个是排列另一个。


    我会使用随机算法,它的错误概率很低。

    关键是使用通用哈希函数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    def hash(array, hash_fn):
      cur = 0
      for item in array:
        cur ^= hash_item(item)
      return cur

    def are_perm(a1, a2):
      hash_fn = pick_random_universal_hash_func()
      return hash_fn(a1, hash_fn) == hash_fn(a2, hash_fn)

    如果数组是排列,它总是正确的。如果它们是不同的,算法可能会错误地说它们是相同的,但这样做的概率非常低。此外,通过在同一个输入上问许多are perm()问题,您可以通过线性工作量获得错误概率的指数下降,如果它曾经说不,那么它们肯定不是彼此的排列。


    您可以将这两个数组中的一个转换为就地哈希表。这不完全是O(N),但在非病理情况下会接近。

    只需使用[number%n]作为所需的索引或在从该索引开始的链中。如果必须替换任何元素,可以将其放在违规元素开始的索引处。冲洗,冲洗,重复。

    更新:这是一个类似的(n=m)哈希表,它确实使用了链接,但它可以降级为开放寻址。