繁体   English   中英

如何从给定范围 Q 查询中创建数组 [1, 2, 3, 1],其中 N 个元素每个 [1, 3] [2, 3] [3, 4] 小于 O(QN)?

[英]How to create array [1, 2, 3, 1] from given range Q queries with N elements each [1, 3] [2, 3] [3, 4] in less than O(QN)?

我有 Q 个查询,每个查询包含 N 个元素。 查询将是范围。

[1, 3] [2, 3] [3, 4]

如果我们展平范围查询,我们会得到这个。

1, 2, 3, 2, 3, 3, 4

我正在尝试找到一种解决方案来创建下面给出的数组。

[1, 2, 3, 4] --> elements in range
[1, 2, 3, 1] --> their frequency array

解释 -> 元素 1 在范围查询中只出现一次,类似地,2 在范围查询中出现两次,依此类推,最后 4 只出现一次。

如上所述,它给出了一组元素频率。

但是将范围展平并对其进行迭代以创建数组的时间复杂度为 O(QN) [1, 2, 3, 2, 3, 3, 4] --> [1, 2, 3, 1]

我没有优化它,我的问题是 - 我们如何才能以尽可能低的时间复杂度(至少小于 O(QN)?

我看到了两种可能的方法。 第一个假设通过每个查询的全部范围进行一次迭代。 它在小范围内有效,但并不比 O(QN) 好:

int[] freqCount1 (final List<int[]> queries){
    Map<Integer, Integer> results = new HashMap<>();
    for(int[] q : queries){
        for(int i = q[0]; i <= q[1]; i++){
            if (!results.containsKey(i)){
                results.put(i, 1);
            }
            else {
                results.put(i, results.get(i) + 1);
            }
        }
    }

    int[] count = new int[results.size()];
    List<Integer> resultsValues = new ArrayList<>(results.values());
    for (int i = 0; i < resultsValues.size(); i++){
        count[i] = resultsValues.get(i);
    }
    return count;
}

第二种方法假定完全确定所有查询的范围,然后遍历范围中的每个元素,检查它是否包含在每个查询中。 在这种方法中,您不需要遍历每个查询的全部范围,所以我相信这低于 O(QN),假设范围在某种程度上重叠。

int[] freqCount2 (final List<int[]> queries){
    int min = queries.stream().map(q -> q[0]).min(Integer::compareTo).get();
    int max = queries.stream().map(q -> q[1]).max(Integer::compareTo).get();
    int range = max - min + 1;

    int[] entries = new int[range];
    List<Integer> countList = new ArrayList<>();

    for (int i = 0; i < range; i++){
        entries[i] = i + min;
        countList.add(0);
    }

    for (int[] q : queries) {
        for (int i = 0; i < range; i++) {
            if (entries[i] >= q[0] && entries[i] <= q[1]) {
                countList.set(i, countList.get(i) + 1);
            }
        }
    }

    List<Integer> countListFiltered = countList.stream()
            .filter(integer -> integer > 0)
            .collect(Collectors.toList());
    int[] count = new int[countListFiltered.size()];
    for (int i = 0; i < countListFiltered.size(); i++){
        count[i] = countListFiltered.get(i);
    }
    return count;
}

我在实践中进行了测试,并以您的示例为例,第一种方法要快得多,但是由于范围长且重叠,第二次获胜(我测试了[4,50000] [300000,500000] [2,100000] [3,800] [5,100000] [6,100000] [70000,900000] [8,100000] )

使用扫描应该可以达到 O(Q log(Q) + N) 。 基本思想是将区间放在数轴上。 间隔的开始和结束按升序处理,同时保持“尚未关闭的间隔”的计数。

下面的代码演示了这一点:

import java.util.*;
import java.util.stream.IntStream;

public class Ranges {

    public static void main(String[] args) {
        List<Range> ranges = List.of(
            new Range(2,7), new Range(1,6), new Range(7, 8), new Range(1, 9)
        );
        System.out.println(ranges);
        List<Event> events = createEvents(ranges);
        int open = 0;
        int start = 0;
        for (Event event : events) {
            switch (event.type()) {
                case START:
                    if (open > 0) {
                        int end = event.value();
                        output(start, end, open);
                        start = end;
                    } else {
                        start = event.value();
                    }
                    open++;                    
                    break;
                case STOP:
                    int end = event.value();
                    if (open == 1 || start != end) {
                        output(start, end + 1, open);
                        start = end + 1;
                    }
                    open--;                    
                    break;
            }
        }
    }   
    
    static List<Event> createEvents(List<Range> ranges) {
        List<Event> events = new ArrayList<>(); 
        for (Range range : ranges) {
            events.add(new Event(EventType.START, range, range.start()));
            events.add(new Event(EventType.STOP, range, range.end()));
        }
        Collections.sort(events);        
        return events;
    }
    
    static void output(int start, int end, int count) {
        IntStream.range(start, end).forEach(value -> System.out.printf("%d %d \n", value, count));        
    }    
    
    /* helper types */       
    
    enum EventType {START, STOP}
    static class Range {
        int start, end;
        
        Range(int start, int end) {
            this.start = start;
            this.end = end;
        }
        
        int start() { return start; }
        int end() { return end; }
        
        public String toString() {
            return "[" + start + ", " + end + "]"; 
        }
    }
    
    static class Event implements Comparable<Event> {
        EventType type;
        Range range;
        int value;
        
        Event(EventType type, Range range, int value) {
            this.type = type;
            this.range = range;
            this.value = value;
        }
        
        @Override
        public int compareTo(Event e) {
            return Integer.compare(value, e.value);
        }
        
        EventType type() { return type; }
        Range range() { return range; }
        int value() { return value; }
    }         
}

输出

[[2, 7], [1, 6], [7, 8], [1, 9]]
1 2
2 3
3 3
4 3
5 3
6 3
7 2
8 2
9 1

(第一行是输入;接下来每一行的数字和计数)

复杂性是通过在 O(Q log(Q)) 时间内排序并通过在 O(N) 中发出每个数字的计数来确定的。 如果我没记错的话,复杂度应该是 O(Q log(Q) + N)。

暂无
暂无

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

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