[英]Efficient algorithm to find number of elements less than a query
我有两个未排序的数组a
和b
。 对于每个元素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
迭代。 我该怎么做才能加快速度?
编辑:考虑到以后的评论,我刚开始就提出了一些更新; 将我的第一句话留在底部。
因此,这里的核心方面是:
好,关于您的问题。 事实是:实际上,这只是“工作”。 那里没有魔术。 由于您有两个非常大的数组,因此对未排序的数据进行操作绝对是不行的。
因此,首先对两个数组进行排序。
然后,您遍历第一个数组,在执行此操作的同时,还要查看第二个数组:
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
数组具有非负数时,该方法才有效。 即使未对输入数组( a
和b
)进行排序,该算法也有效。
伪码
b
的max
元素。 max + 1
的新数组c
,并将1
放在位置c[b[i]]
。 创建一个大小为max + 1
的新数组d
,并将其填充如下:
d[0]=0;
d[i]=d[i-1] + c[i];
创建一个大小为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.