查找少于查询的元素数的高效算法

时间:2016-11-12 10:30:39

标签: java algorithm dynamic-programming

我有两个未排序的数组ab。对于每个元素a[i],我需要找到b[j]元素的数量b[j] < a[i]。此外,b可能包含不应计算的重复项。两个阵列都可能非常大。

我尝试过(针对单个查询x

public static void main(String arg[]) {
    int x = 5;
    int b[] = {2, 4, 3, 4, 11, 13, 17};
    Arrays.sort(b);
    int count = 0;
    for(int i = 0; i < b.length; ++i) {
        if(b[i] < x) {
            if(i == 0)
                ++count;
            else {
                // this check avoids counting duplicates
                if(b[i - 1] != b[i])
                    ++count;
            }
        } else {
            break;
        }
    }
    System.out.println(count);
}

我的问题是,当迭代地查询a的所有元素时,这不能很好地执行。我该怎么做才能加快速度呢?

5 个答案:

答案 0 :(得分:5)

编辑:鉴于后面的评论,我刚刚在开始时提出了一些更新;将我的第一个文字留在底部。

所以,这里的核心方面是:

  1. 你来到这里有一些问题X,但进一步询问告诉我们你实际上有一些问题Y要解决。这是应该尽量避免的事情:来到这里(或者当你自己处理问题时!)......那么你应该能够清楚地描述你已经或打算解决的问题。我不是在指点;只是表示您应该努力,以确保您了解真正的问题是什么。
  2. 询问我们如何处理数据中的重复数字这一事实也可以看出这一点。呃,先生:这是你的问题。我们不知道你为什么要数这些数字;我们不知道您的数据来自哪里;以及最终解决方案应如何处理重复条目。从这个意义上说,我只是改写第一段:必须澄清你的要求。我们无法在所有的帮助下完成该部分。你看,你只提到了第二个数组中的重复项。第一个人呢?!
  3. 好的,关于你的问题。事情是:实际上,这只是&#34;工作&#34;。那里没有魔力。由于您有两个非常大的数组,处理未排序的数据是绝对禁止的。

    因此,您首先要对两个数组进行排序。

    然后迭代第一个数组,同时这样做,你也会查看第二个数组:

    int indexWithinB = 0;
    int counterForCurrentA = 0; // and actually ALL values from a before
    for (int i=0; i<a.length; i++) {
      int currentA = a[i];     
      while (b[indexWithinB] < currentA) {
        if (indexWithinB > 0) { // check required to avoid using 0-1
          if (b[indexWithinB-1] != b[indexWithinB] { // avoid counting duplicates!
            counterForCurrentA++;
          }
        }
        indexWithinB++;
      }
      // while loop ended, this means: b[indexWithinB] == or > currentA
      // this also means: counterForCurrentA ... should have the correct value
    }
    

    以上显然是伪代码。它旨在让你继续前进;而且很可能是那里存在微妙的错误。例如,正如安德烈亚斯指出的那样:上面需要加强以检查b.length。但这仍然是读者的锻炼。

    这就是我的意思&#34;只是工作&#34;:您只需坐下来,编写测试用例并优化我的草案算法,直到它为您完成工作。如果你发现它最初很难编程,那么拿一张纸,放下两个带数字的数组......然后手动计算。

    最后提示:我建议编写大量的单元测试来测试你的算法(这样的东西完美用于单元测试);并确保你在这些测试中拥有所有的角落案例。在进入10 ^ 5元素阵列之前,您希望100%确定您的算法是正确的!

    在这里,正如所承诺的那样,原来的答案是:

    简单地说:迭代和计数是解决此问题的最有效方法。因此,在上述情况下,省略排序可能会缩短整体执行时间。

    逻辑非常简单:为了知道小于x的数字的数量......你必须看看所有。因此,您必须迭代整个数组(当该数组未排序时)。

    因此,鉴于您的初始陈述,除了:迭代和计数之外没有别的东西。

    当然,如果您需要多次计算......最初可能值得对数据进行排序。因为那时你可以使用 binary search ,并且在没有迭代所有数据的情况下获得那些数量。

    并且:是什么让你认为用10 ^ 5元素迭代一个数组是个问题?换句话说:您是否只是担心潜在的性能问题,或者您是否存在真正的性能问题?你看,在某些时候你可能不得不创建填充那个数组。这肯定比简单的for循环计数条目需要更多的时间(和资源)。老实说:除非我们正在谈论一些小型嵌入式设备...... 10 ^ 5个元素......即使在使用稍微过时的硬件时,几乎没有

    最后:当您担心运行时时,简单的答案是:对输入数据进行切片,并使用2,4,8,...线程并行计算每个切片!但正如所说:在编写代码之前,我会做一些分析确保你真的需要花费宝贵的开发时间。不要解决假设的性能问题;专注于真正对您或您的用户至关重要的那些!

答案 1 :(得分:1)

使用x对数组中的每个项目进行Comap将花费O(n)时间。对数组进行排序将采用O(n log n),然后您可以使用二进制搜索,即O(log n),并获得总计O(n log n)。所以最有效的方法也是微不足道的 - 只需循环通过数组并将每个项目与x进行比较。

public static void main(String arg[] ){
    int b[]={2, 4, 3, 4, 11, 13, 17};
    int x=5;
     int count=0;
     for(int i=0;i<b.length;i++){
         if(b[i]<x){          
             count++;
         }
     }
     System.out.println(count);
}

答案 2 :(得分:0)

我建议您考虑以下方法,但仅当b数组具有非负数时才有效。即使输入数组(ab)未排序,该算法仍然有效。

<强>伪代码

  1. 获取数组max的{​​{1}}元素。
  2. 创建一个大小为b的新数组c,并将max + 1置于1位置。
  3. 创建一个大小为c[b[i]]的新数组d,并将其填充如下:

    max + 1
    d[0]=0;

  4. 创建一个大小为d[i]=d[i-1] + c[i];的新数组e,并将其填充如下:

    n
    if(a[i] > max) then e[i] = last(d)

  5. otherwise e[i]=d[a[i]-1];数组表示解决方案:它在第i个位置包含e数组的数字的计数器,低于数组b的第i个元素。 这个例子应该比伪代码更具说明性:

    a

    <强>复杂性

    a = [5, 1, 4, 8, 17, 12, 22]
    b = [2, 4, 3, 4, 11, 13, 17]
    c = [0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1]
    d = [0, 0, 1, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 5, 5, 5, 5, 6]
    e = [3, 0, 2, 3, 5, 4, 6]
    

    如果输入数组Steps 1, 2 and 4 are O(n). Step 3 is O(max(b)) 仅包含&#34; short&#34;算法在b中执行的数字(max(b)与n大小的顺序相同)。 可以优化该算法,创建大小为O(n)的数组,并考虑max-min+1数组低于0的所有元素的计数器a

    一个简单的java实现:

    min(b)

答案 3 :(得分:0)

对于更大的排序集,我们需要使用分而治之原理来加快搜索速度。这是我的解决方案,具有O(logn)时间复杂度和O(n)空间复杂度。

public static void main(String arg[]) {
    int x = 5;
    int b[] = {2, 4, 3, 4, 11, 13, 17};
    int high = b.length - 1;
    int low = 0;

    while (high >= low) {
      int mid = (high + low) / 2;
        if (b[mid] < x)
          low = mid + 1;
        else
          high = mid - 1;
    }
  System.out.println(low);

}

答案 4 :(得分:-1)

这应该是一个可能的解决方案。所述&#34;昂贵&#34;任务是列表的排序。必须在for循环之前对bost列表进行排序。确保使用快速机制来执行排序。如上所述,对数组/数组列表进行排序是一种非常扩展操作,特别是如果有许多值需要排序。

public static void main(String[] args) throws IOException {
    // int x = 5;
    int a[] = { 1, 2, 3, 4, 5 };
    int b[] = { 2, 4, 3, 4, 11, 13, 17 };
    List<Integer> listA = new LinkedList<>();
    for (int i : a) {
        listA.add(i);
    }
    List<Integer> listB = new LinkedList<>();
    for (int i : b) {
        listB.add(i);
    }
    Collections.sort(listA);
    Collections.sort(listB);
    int smallerValues = 0;
    int lastValue = 0;
    Iterator<Integer> iterator = listB.iterator();
    int nextValue = iterator.next();
    for (Integer x : listA) {
        while (nextValue < x && iterator.hasNext()) {
            lastValue = nextValue;
            nextValue = iterator.next();
            if (nextValue > lastValue) {
                smallerValues++;
            }
        }
        System.out.println(x + " - " + smallerValues);
    }
}