在数组中查找数字及其平方的算法

时间:2010-02-01 20:11:12

标签: arrays algorithm

我有一个整数数组,我需要一个O(n)算法来查找数组是否包含数字及其平方;一对就足够了。

我试图自己做,但我只是设法找到O(n 2 )的解决方案。

我考虑过使用计数排序,但内存使用量太大了。

12 个答案:

答案 0 :(得分:12)

创建一个两倍于输入数组长度的新数组。 O(2N)
复制O(N)中的所有数字 复制O(N)中数字的平方 基数排序(我们可以因为它们都是整数)O(N)
迭代一次以查看是否有两个数字相同,一个接着另一个O(N)
利润! O(1)

答案 1 :(得分:4)

基本上有两种方法可以做到这一点。

  1. 对数组进行排序,然后对每个数字的平方执行二进制搜索。整体复杂性将是O(nlogn),但它需要排序,这将破坏原始排序(这可能对您的情况很重要)。

  2. 将数组的所有项插入哈希表(或任何快速set数据结构)。然后再次迭代数组的元素,检查它的方块是否存在于哈希表中。使用散列表给出了O(n)的总体复杂度,但是您将需要O(n)额外空间。您还可以使用基于树的set(例如,在C ++中为std::set或在Java中为TreeSet),这会给您带来O(nlogn)的复杂性。

答案 2 :(得分:3)

如果我们被允许接受输入可以按照基数排序以O(N)排序,我会对Chris的解决方案有所改进:

  • radix对输入进行排序。
  • 对于结果的第一个元素,线性向前搜索,直到我们找到它的正方形(在这种情况下,使用true停止),或者结束(在这种情况下,以false结尾)或者大于正方形的值(在这种情况下,继续搜索已排序数组的第二个和后续元素的平方。)

两个“指针”中的每一个都严格向前移动,因此总体复杂度为O(N),假设基数排序为O(N)并且平方和比较为O(1)。据推测,无论谁提出这个问题,都应该做出这些假设。

回答提问者对另一个答案的评论:如果输入中的整数没有限制,那么我认为不能这样做。只计算整数的平方需要大于线性时间(至少:没有用于乘法的线性算法),所以考虑一个大小为n位的输入,由两个大小为n / 3位且{{1}的整数组成比特。测试一个是否是另一个的平方不能在O(n)中完成。我认为。我错了。

答案 3 :(得分:1)

虽然我无法添加上述建议,但您可以通过首先查找数据集中的最小值和最大值(均为O(n))并将搜索范围限制在该范围内来缩短平均运行时间。例如,如果最大值是620,我知道没有25或更大的整数在列表中有一个正方形。

答案 4 :(得分:1)

你可以用几个帮助你的哈希集来做到这一点。

迭代时, 如果值在方块hashset中,则您有一对(value是先前找到的值的平方) 如果square在值hashset中,你有一对(这个值的平方已经传递) 否则将值存储在一个中,将正方形存储在另一个中。

答案 5 :(得分:1)

就我个人而言,我认为Anon的答案(带有“正方形”的小算法)比看起来更有用:从中删除“从正方形中移除所有小于e”的行,算法可以处理未排序的输入阵列。

如果我们假设具有足够空间的典型家庭作业机器,则可以将“正方形”数据结构建模为布尔标志数组,从而产生真正的O(1)查找时间。

答案 6 :(得分:1)

如果我们使用C / C ++ 32位无符号整数,则可以存储的最大值是:4294967295 =(2 <&lt; 32)-1。我们可以存储其正方形的最大数字是(1 <&lt; 16)-1 = 65535。现在,如果创建一个位数组并存储在数组中,我们是否已经看到了数字和/或它的平方(每个“槽”2位),我们可以将总存储量降低到65535/4 = 16384字节。

IMO这不是过多的内存消耗,所以我们应该能够在没有基数排序的情况下做到这一点。 O(N)算法可能如下所示:

uint32_t index(uint32_t i ) { return i/4; }
unsigned char bit1( uint32_t i ) { return 1<<( (i%4)*2 ); }
unsigned char bit2( uint32_t i ) { return 1<<( (i%4)*2 +1 ); }


bool hasValueAndSquare( std::vector<uint32_t> & v )
{
   const uint32_t max_square=65535;

   unsigned char found[(max_square+1)/4]={0};
   for(unsigned int i=0; i<v.size(); ++i)
   {
      if (v[i]<=max_square)
      {
          found[ index(v[i]) ] |= bit1(v[i]);
          if ((found[ index(v[i])] & bit2(v[i])) == bit2(v[i])) return true;
      }
      uint32_t w = (uint32_t)round(sqrt(v[i]));
      if( w*w == v[i] )
      {
          found[ index(w) ] |= bit2(w);
          if ((found[index(w)] & bit1(w)) == bit1(w)) return true;
      }
    }
    return false;
 }

这未经过测试,未经过优化,并且正确的整数平方根会更好。 但是编译器应该内联所有的位访问函数 - 所以它们就可以了。

请注意,如果我们使用64位整数,内存消耗会变得更大,而不是16Kb的数组,我们需要一个1Gb的数组 - 可能不太实用。

答案 7 :(得分:1)

没有排序,可以使用重复项:

迭代数组以找到最小和最大的整数。的 O(n)的
创建差异大小的位数组。 O(1)时间,O(k)空间
(现在每个可能的最小值和最大值之间的整数在数组中有一个对应的位)
迭代旧数组,将找到的每个整数对应的位设置为1. O(n)
再次迭代旧数组,检查整数的平方是否有相应的位设置。的 O(n)的

(虽然我没有排序,但可以非常容易地修改此算法以创建在{O(n + k)时间和O(k)空间中排序的a sorting algorithm < / p>

答案 8 :(得分:1)

优化备注

可以通过记录三个事实来优化hashset和radix排序算法:

  1. 奇数和偶数值可以单独处理
  2. 计算整数平方根是一种非常快速的操作(通常包含3-5个除法和几个加法)
  3. 缓存局部性对于这两种算法都很重要
  4. 下面的优化算法通常会比未经优化的情况快5倍,使用的RAM不到一半。在数据大小类似于L2 / L3高速缓存大小的某些情况下,它们可以执行速度提高100倍或更多。

    基于基数排序的优化算法

    数据结构是五个整数列表:IN,Aodd,Bodd,Aeven,Beven A和B列表使用IN的整数大小的一半。 (例如,如果IN = 64位,A&amp; B = 32位)

    1. 扫描列表IN以查找最大奇数和偶数MAXodd和MAXeven
    2. 让LIMITodd = floor(sqrt(MAXodd))
    3. 让LIMITeven = floor(sqrt(MAXeven))
    4. 对于列表IN中的每个数字:a。如果为正,则计算平方根。如果确切,请添加平方根以列出Aodd / Aeven。湾如果数字是&gt; = 0且&lt; = LIMITodd / LIMITeven,请将其添加到列表Bodd / Beven
    5. Radix排序列表Aodd和Bodd只使用log2(LIMITodd)位
    6. 线性扫描Aodd和Bodd的比赛
    7. 基数排序列表Aeven和Beven仅使用log2(LIMITeven)位
    8. 线性扫描Aeven和Beven的比赛
    9. 如果线性扫描找到匹配项,请立即返回该匹配项。

      这比简单的基数排序算法快得多的原因是:

      • 典型排序的数组的值小于1/4,每个整数只需要一半的位数,所以在给定的排序中使用的RAM总数不到1/8,这对于缓存。
      • 基数排序在更少的位上完成,导致更少的传递,因此即使它确实超过了你的L1或L2缓存,你读RAM的次数也少,而你读的内存要少得多
      • 线性扫描通常要快得多,因为A列表只包含精确的平方根,而B列表只包含小值

      基于hashset的优化算法

      数据结构是整数IN列表,加上两个哈希集A和B. A和B集使用IN的整数大小的一半

      1. 扫描列表IN以查找最大奇数和偶数MAXodd和MAXeven
      2. 让LIMITodd = floor(sqrt(MAXodd))
      3. 让LIMITeven = floor(sqrt(MAXeven))
      4. 对于列表IN中的每个奇数:a。如果为正,则计算平方根。如果确切,检查B&amp;中是否存在平方根。如果为true则返回,否则将其添加到A.如果数字是> = 0且&lt; = LIMITodd / LIMITeven,请检查它是否存在于A&amp;如果为true则返回,否则将其添加到B。
      5. 清除A和B并重复步骤4以获得偶数
      6. 这比直接的hashset算法更快的原因是:

        • 哈希值通常是RAM量的1/8,从而带来更好的缓存性能
        • 只有精确的正方形和小数字才有哈希集条目,因此散列和添加/删除值的时间要少得多

        此处还有一个额外的小优化:A和B可以是单个hashset,它存储每个条目的位标志,表示整数是在A还是B中(它不能同时存在,因为那时算法会终止的。)

答案 9 :(得分:0)

如果我正确理解了问题,您必须检查数组中是否有指定的数字。并没有找到阵列中的所有数字也在数组中有它们的正方形。 只需维护两个布尔值(一个用于检查数字是否已找到,另一个用于方块),迭代数组中的元素并测试每个元素。返回两个布尔值的AND。

在伪代码中:

bool ArrayContainsNumberAndSquare(int number, int[] array):
boolean numberFound, squareFound;
int square = number * number;
foreach int i in array
(
  numberFound = numberFound || i == number;
  squareFound = squareFound || i == square;
)
return numberFound && squareFound;

答案 10 :(得分:0)

1)使用hashmap,你得到O(n)。

2)如果你在2套上使用​​std :: set:平均值和赔率,你可以得到

2 * O((N / 2)的log(n / 2))= O(n日志(N / 2))

假设平均值大于赔率

答案 11 :(得分:-1)

如果数组未排序,您将无法执行O(n)。

如果它已经排序,你可以使用该属性一次完成,如下所示:

foreach e in array
    if squares contains e
        return true
    remove all less than e from squares
    add e * e to squares
return false

例如,squares是HashSet。

如果它没有排序,你可以在O(n log n)中对它进行排序,然后使用这种方法检查正方形,这仍然比足够大的数据集上的天真解决方案更快。