如何在Java 8中找到N个数字中最大的M个数字?

时间:2015-06-11 03:57:40

标签: java algorithm java-8 java-stream

IntStream可能是最简单的方法,但我只能选择最小的M个数字,如下所示:

public class Test {
    private static final int[] arr = {5, 3, 4, 2, 9, 1, 7, 8, 6};

    public static void main(String[] args) throws Exception {
        System.out.println(Arrays.asList(IntStream.of(arr).sorted().limit(5).boxed().toArray()));
    }
}

btw,考虑算法复杂度并假设N>> M,“排序+限制”方法只有O(N log(N))的复杂性。

我认为最好的复杂性可能达到O(N log(M)),但我不知道Java 8是否有这种流方法或收集器。

5 个答案:

答案 0 :(得分:5)

如果必须使用Streams:

IntStream.of(arr).sorted().skip(N-M)

否则使用PriorityQueue并为自己写一个反转Comparator。插入将是 O(N(log(N))并且M元素的删除将是 O(M(log(N))。不是你要求的,但是也许够近了。

答案 1 :(得分:3)

EJP没错,我测试了它 - 当输入为2时产生8和9。

import java.util.stream.IntStream;
public class Test {
    private static final int[] arr = {5, 3, 4, 2, 9, 1, 7, 8, 6};

    public static void main(String[] args) throws Exception { 
        int n = Integer.parseInt(args[0]);
        System.out.println("Finding "+n+" largest numbers in arr");
        IntStream.of(arr).sorted().skip(arr.length-n).boxed().forEach(big -> System.out.println(big));
    }
}

答案 2 :(得分:2)

如果您已在项目中使用google guava,则可以利用MinMaxPriorityQueue

Collection<..> min5 = stream.collect(
    toCollection(MinMaxPriorityQueue.maximumSize(5)::create)
);

答案 3 :(得分:1)

可以使用JDK PriorityQueue创建自定义收集器来解决您的任务:

public static <T> Collector<T, ?, List<T>> maxN(Comparator<? super T> comparator, 
                                                int limit) {
    BiConsumer<PriorityQueue<T>, T> accumulator = (queue, t) -> {
        queue.add(t);
        if (queue.size() > limit)
            queue.poll();
    };
    return Collector.of(() -> new PriorityQueue<>(limit + 1, comparator),
            accumulator, (q1, q2) -> {
                for (T t : q2) {
                    accumulator.accept(q1, t);
                }
                return q1;
            }, queue -> new ArrayList<>(queue));
}

用法:

int[] arr = {5, 3, 4, 2, 9, 1, 7, 8, 6};
System.out.println(IntStream.of(arr).boxed().collect(maxN(Comparator.naturalOrder(), 2)));
// [8, 9]
System.out.println(IntStream.of(arr).boxed().collect(maxN(Comparator.reverseOrder(), 3)));
// [3, 1, 2]

对于大数据集和小限制可能更快,因为它不排序。如果需要排序结果,可以将排序步骤添加到finisher

答案 4 :(得分:1)

您可以通过创建值的直方图来实现复杂性目标:

public static IntStream maxValues(IntStream source, int limit) {
    TreeMap<Integer,Integer> m=new TreeMap<>();
    source.forEachOrdered(new IntConsumer() {
        int size, min=Integer.MIN_VALUE;
        public void accept(int value) {
            if(value<min) return;
            m.merge(value, 1, Integer::sum);
            if(size<limit) size++;
            else m.compute(min=m.firstKey(), (k,count)->count==1? null: count-1);
        }
    });
    if(m.size()==limit)// no duplicates
        return m.keySet().stream().mapToInt(Integer::valueOf);
    return m.entrySet().stream().flatMapToInt(e->{
        int value = e.getKey(), count = e.getValue();
        return count==1? IntStream.of(value): IntStream.range(0, count).map(i->value);
    });
}

它创建一个从int值到其相应出现次数的映射,但是将其内容限制为所需的值数,因此,它的操作具有O(log(M))复杂度(最坏情况,如果没有重复),并且,因为对每个值执行一次操作,总体复杂度为O(N×log(M)),如您所愿。

您可以使用原始数组测试它

int[] arr = {5, 3, 4, 2, 9, 1, 7, 8, 6};
maxValues(Arrays.stream(arr), 3).forEach(System.out::println);

但是为了测试一些极端情况,您可以使用包含重复项的数组,如

int[] arr = {8, 5, 3, 4, 2, 2, 9, 1, 7, 9, 8, 6};
// note that the stream of three max elements contains one of the two eights

如果您努力获得最佳性能,使用原始数据类型替换具有适当数据结构的装箱树图可能是可行的,但这可能是次要的性能优化,因为此解决方案已经解决了复杂性问题。

顺便说一句,这个解决方案适用于任意流,即不需要知道N的值。