I have a Q number of queries containing N elements each. Queries will be range.
[1, 3] [2, 3] [3, 4]
If we flatten out range queries we will get this.
1, 2, 3, 2, 3, 3, 4
I am trying to find a solution to create a array given below.
[1, 2, 3, 4] --> elements in range
[1, 2, 3, 1] --> their frequency array
Explanation -> Element 1 comes only one time in range queries, similarly, 2 comes two times in range queries, and so on, and at last 4 comes only one time.
Which gives a array of elements frequency as mentioned above.
But flattening out the range and iterating over it to create a array has a time complexity of O(QN) [1, 2, 3, 2, 3, 3, 4] --> [1, 2, 3, 1]
I failed to optimize it, my question is - How can we do it in the least possible time complexity (at least less than O(QN)?
CodePudding user response:
I see two possible approaches. The first assumes one iteration through full range of every query. It's efficient at small ranges, but not better than 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;
}
The second approach assumes determining the range for all queries altogether and then iterating through each element from the range, checking whether it's included in each of the queries. In this approach you don't need to iterate through full range of each query, so I believe this is below O(QN), assuming that the ranges overlap to some extent.
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;
}
I tested in practice and with your example the first approach is much faster, but with long and overlapping ranges the second wins (I tested for [4,50000] [300000,500000] [2,100000] [3,800] [5,100000] [6,100000] [70000,900000] [8,100000]
)
CodePudding user response:
It should be possible to reach O(Q log(Q) N) using a sweep. The basic idea is to place the intervals on a number line. Starts and ends of the intervals are processed in ascending order while maintaining a count of "not yet closed intervals".
The following code demonstrates this:
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; }
}
}
Outputs
[[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
(first line is the input; number and count on each following line)
Complexity is determined by sorting in O(Q log(Q)) time and by emitting the counts for each number in O(N). If I'm not wrong, complexity should be O(Q log(Q) N).