我试图从整数数组输入中找到前4个最大值。例如,对于给定的输入数组{1232,-1221,0,345,78,99}将返回{1232,345,99,78}作为前4个最大值。我用下面的方法解决了这个问题。但我仍然不满足于它的时间效率。当输入变大时,是否有机会更多地优化方法?任何线索都非常感谢。谢谢。
public int[] findTopFourMax(int[] input) {
int[] topFourList = { Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE };
for (int current : input) {
if (current > topFourList[0]) {
topFourList[3] = topFourList[2];
topFourList[2] = topFourList[1];
topFourList[1] = topFourList[0];
topFourList[0] = current;
} else if (current > topFourList[1]) {
topFourList[3] = topFourList[2];
topFourList[2] = topFourList[1];
topFourList[1] = current;
} else if (current > topFourList[2]) {
topFourList[3] = topFourList[2];
topFourList[2] = current;
} else if (current > topFourList[3]) {
topFourList[3] = current;
}
}
return topFourList;
}
答案 0 :(得分:13)
最简单(虽然不是最有效)的方式是按照包含最后4个元素的子数组对数组进行排序。
您可以使用Arrays.sort()
进行排序,并使用Arrays.copyOfRange()
来获取子阵列。
int[] arr = new int[] {1232, -1221, 0, 345, 78, 99};
Arrays.sort(arr);
int[] top4 = Arrays.copyOfRange(arr, arr.length-4,arr.length);
System.out.println(Arrays.toString(top4));
要获得更有效的解决方案,可以维持 min-heap 的前K个元素,或使用 selection algorithm 来查找前4个元素。这两种方法在this thread中描述。
虽然选择算法提供了O(n)
解决方案,但是最小堆解决方案(O(nlogK)
)应该具有更好的常量,特别是对于小k
,可能会更快。< / p>
P.S。 (编辑):
对于4个元素,您可能会发现调用循环4次,并在每个循环中找到最大值(并在每次迭代中将旧的最大值更改为-infinity)将比更“复杂”的方法更有效,因为它需要顺序读取并且具有相当小的常量。对于较大的k
,这当然不适用,对于O(n^2)
k->n
EDIT2:基准测试:
为了它的乐趣,我在附加的代码上运行了一个基准测试。结果是:
[naive, sort, heap] = [9032, 214902, 7531]
我们可以看到天真和堆比基于排序的方法好得多,并且天真比基于堆的稍微慢一些。我做了一个wilcoxon test来检查天真和堆之间的差异是否具有统计意义,我得到了3.4573e-17
的P_Value。这意味着两种方法“相同”的概率是3.4573e-17(非常小)。由此我们可以得出结论 - 基于堆的解决方案提供了比天真和排序解决方案更好的性能(我们凭经验证明了它!)。
附件:我使用的代码:
public static int[] findTopKNaive(int[] arr, int k) {
int[] res = new int[k];
for (int j = 0; j < k; j++) {
int max=Integer.MIN_VALUE, maxIdx = -1;
for (int i = 0; i < arr.length; i++) {
if (max < arr[i]) {
max = arr[i];
maxIdx = i;
}
}
arr[maxIdx] = Integer.MIN_VALUE;
res[k-1-j] = max;
}
return res;
}
public static int[] findTopKSort(int[] arr, int k) {
Arrays.sort(arr);
return Arrays.copyOfRange(arr, arr.length-k,arr.length);
}
public static int[] findTopKHeap(int[] arr, int k) {
PriorityQueue<Integer> pq = new PriorityQueue<Integer>();
for (int x : arr) {
if (pq.size() < k) pq.add(x);
else if (pq.peek() < x) {
pq.poll();
pq.add(x);
}
}
int[] res = new int[k];
for (int i =0; i < k; i++) res[i] = pq.poll();
return res;
}
public static int[] createRandomArray(int n, Random r) {
int[] arr = new int[n];
for (int i = 0; i < n; i++) arr[i] = r.nextInt();
return arr;
}
public static void main(String... args) throws Exception {
Random r = new Random(1);
int k = 4;
int repeats = 200;
int n = 5000000;
long[][] results = new long[3][repeats];
for (int i = 0; i < repeats; i++) {
int[] arr = createRandomArray(n, r);
int[] myCopy;
myCopy = Arrays.copyOf(arr, n);
long start = System.currentTimeMillis();
findTopKNaive(myCopy, k);
results[0][i] = System.currentTimeMillis() - start;
myCopy = Arrays.copyOf(arr, n);
start = System.currentTimeMillis();
findTopKSort(myCopy, k);
results[1][i] = System.currentTimeMillis() - start;
myCopy = Arrays.copyOf(arr, n);
start = System.currentTimeMillis();
findTopKHeap(myCopy, k);
results[2][i] = System.currentTimeMillis() - start;
}
long[] sums = new long[3];
for (int i = 0; i < repeats; i++)
for (int j = 0; j < 3; j++)
sums[j] += results[j][i];
System.out.println(Arrays.toString(sums));
System.out.println("results for statistic test:");
for (int i = 0; i < repeats; i++) {
System.out.println(results[0][i] + " " + results[2][i]);
}
}
答案 1 :(得分:2)
你应该看看this answer by Peter Lawrey。基本上,我们的想法是遍历您的数组,将每个元素添加到SortedSet
并通过删除每次迭代中的最小元素将大小保持为4。这个过程是O(n),即使在最坏的情况下,与典型的O(n logn)和O(n 2 )最坏情况相比,对数组进行完全排序。
final List<Integer> input = new ArrayList(Arrays.asList(1232, -1221, 0, 345, 78, 99));
final NavigableSet<Integer> topFour = new TreeSet<>();
for (int i : input) {
topFour.add(i);
if (topFour.size() > 4) topFour.remove(topFour.first());
}
System.out.println(topFour);
答案 2 :(得分:1)
最简单的方法是对数组进行排序并获取第一个/最后4个元素。
最后,最多4个条目可以在任何地方,所以无论你做什么,你需要读取整个数组,它将是一个O(n)操作。
答案 3 :(得分:1)
之前提到的关于对数组进行排序的提法确实提供了最简单的方法,但并不是最有效的方法。
QuickSort(Quickselect)的变体可用于查找集合中的第k个最大/最小值。
http://en.wikipedia.org/wiki/Selection_algorithm
正确的实现允许您在O(n)时间内获得第k个最大值。
基本上你使用枢轴在快速排序中分区,并将每次迭代后的轴位置与你想要的位置(在你的情况下为4)进行比较,如果它相等,则返回位置,否则,将算法应用于正确的一半输入。
当您找到第k个最大值的索引时,您可以再次遍历数组并获得低于input[k]
的值。
这可能对你的情况有点过分,因为你只需要四个,但这是最通用的方法。
如果你不太关心内存,你也可以使用一个有界的PriorityQueue来保存顶部/底部X值,并简单地在Queue中插入所有内容。剩下的就是你感兴趣的价值。
答案 4 :(得分:1)
排序:对数组进行排序并获取最后四个元素
Min Heap:最简单的解决方案是维持最大4的最小堆。
这个解决方案是O(nlogk)的复杂性,其中n是元素的数量,k是你需要的元素的数量。
优先级队列:您可以创建一个具有固定大小的PriorityQueue
和一个自定义比较器,如this question中所述。
选择算法:你可以使用selection algorithm,你可以找到第(nk)个最大元素然后返回高于这个元素的所有元素但是它更难实现。最佳案例复杂性: O(n)
答案 5 :(得分:-1)
float a[] = {1.0f,3.0f,5.0f,6.0f,7.0f,10.0f,11.0f,3.2f,4.0f};
float first =0.0f;
float second=0.0f;
float third =0.0f;
for (int i=0; i<a.length; i++){
if(first < a[i]){
first=a[i];
}
}
System.out.println("first largest is "+first);
for (int j=0; j<a.length; j++){
if(a[j] <first && a[j] > second){
second = a[j];
}
}
System.out.println("second largest is "+second);
for (int k=0;k<a.length; k++){
if(a[k]<second && a[k]>third){
third =a[k];
}
}
System.out.println("third largest is "+third);