繁体   English   中英

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

[英]Efficient algorithm to find number of elements less than a query

我有两个未排序的数组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迭代。 我该怎么做才能加快速度?

编辑:考虑到以后的评论,我刚开始就提出了一些更新; 将我的第一句话留在底部。

因此,这里的核心方面是:

  1. 您来到这里时遇到了问题X,但进一步询问告诉我们,您实际上有一些问题要解决。 那是应该避免的事情:来到这里(或自己解决问题!)...那么您应该能够清楚地描述您已经解决或打算解决的问题。 我不是在这里指指点点; 只是表示您应该努力确保您了解真正的问题是什么。
  2. 正在询问我们如何处理数据中的重复数字这一事实也可以看出这一点。 先生,先生:这是您的问题。 我们不知道您为什么要计算这些数字; 我们不知道您的数据来自哪里; 以及最终解决方案应如何处理重复的条目。 从这个意义上讲,我只是改写第一段: 必须澄清您的要求。 我们不能用这部分的所有帮助。 您会看到:您仅在第二个数组中提到重复项。 那第一个呢?!

好,关于您的问题。 事实是:实际上,这只是“工作”。 那里没有魔术。 由于您有两个非常大的数组,因此对未排序的数据进行操作绝对是不行的。

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

然后,您遍历第一个数组,在执行此操作的同时,还要查看第二个数组:

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
}

上面显然是伪代码。 它旨在使您继续前进; 那里很可能有细微的错误。 例如,正如安德里亚斯(Andreas)所指出的:还需要对上述内容进行增强以检查b.length。 但这留给读者练习。

这就是我所说的“正常工作”的意思:您只需要坐下来,编写测试用例并完善我的算法草稿,直到它为您完成工作即可。 如果您发现很难一开始就编写程序,则拿一张纸,放下两个带有数字的数组...,然后手动进行计数。

最后提示:我建议编写大量的单元测试来测试您的算法(这类内容非常适合单元测试); 并确保您在此类测试中拥有所有重要案例。 您想要在使用10 ^ 5元素数组之前100%确保算法正确!

和这里一样,原始的答案:

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

那里的逻辑真的很简单:为了知道小于x的数字计数,您必须查看所有这些数字。 因此,您必须迭代整个数组(当该数组未排序时)。

因此,给定您的初始声明,没有其他事情了:迭代并计数。

当然,如果您必须多次进行计数...可能值得一开始对数据进行排序。 因为这样您就可以使用二进制搜索 ,并且获得该计数就可以在不迭代所有数据的情况下寻找工作。

并且:是什么让您认为迭代具有10 ^ 5个元素的数组是一个问题? 换句话说:您只是担心潜在的性能问题,还是真正的性能问题? 您会看到,有时可能必须创建填充该数组。 当然,这比简单的for循环对条目进行计数要花费更多的时间(和资源)。 老实说:除非我们使用的是小型嵌入式设备... 10 ^ 5个元素...甚至在使用稍微陈旧的硬件时也几乎没有

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

将数组中的每个项目与x共同映射将花费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);
}

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

伪码

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

    d[0]=0;
    d[i]=d[i-1] + c[i];

  4. 创建一个大小为n的新数组e ,并将其填充如下:

    if(a[i] > max) then e[i] = last(d)
    otherwise e[i]=d[a[i]-1];

e数组表示解决方案:它在第i个位置包含b数组的编号计数器,其数量低于数组a的第i个元素。 此示例应比伪代码更具解释性:

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))

如果输入数组b仅包含“短”数字(max(b)的大小为n的相同顺序),则算法以O(n)执行。 可以对算法进行优化,以创建大小为max-min+1的数组,并为小于min(b) a数组的所有元素考虑计数器0

一个简单的java实现:

int a[] = {5, 1, 4, 8, 17, 12, 22};
int b[] = {2, 4, 3, 4, 11, 13, 17};
int max = Arrays.stream(b).max().getAsInt();
int c[] = new int[max+1];
int d[] = new int[max+1];
int e[] = new int[a.length];
for(int i=0;i<b.length;i++){
    c[b[i]]=1;
}
for(int i=1;i<c.length;i++){
    d[i] = d[i-1] + c[i];
}
for (int i = 0; i<a.length;i++){
    e[i]=(a[i]>max)?d[d.length-1]:d[a[i]-1];
}
System.out.println(Arrays.toString(a));
System.out.println(Arrays.toString(b));
System.out.println(Arrays.toString(c));
System.out.println(Arrays.toString(d));
System.out.println(Arrays.toString(e));

对于更大的排序集,我们需要使用分而治之原理来加快搜索速度。这是我的解决方案,具有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);

}

这应该是一个可能的解决方案。 “昂贵”的任务是对列表进行排序。 Bost列表必须在for循环之前排序。 确保使用快速机制执行排序。 解释说,对数组/数组列表进行排序是一项非常昂贵的操作,尤其是当您必须对许多值进行排序时。

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);
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM