Consider an alternative algorithm
The posted code is slow for large inputs,
because it checks all combinations of parents and children,
even for inputs where the number of answers will be a relatively small set.
I put an emphasis on the last point,
to highlight that when all children are within all parents,
then the answer must contain all pairings.
A more efficient solution is possible for inputs where the number of answers is significantly smaller than all possible pairings. (And without degrading the performance in case the answer is the complete set.)
- Loop over the interesting positions from left to right. An interesting position is where a parent or child interval starts or ends.
- If the position is a parent:
- If this the start of the parent, add the parent to a linked hashset of started parents.
- Otherwise it's the end of the parent. Remove this parent from the linked hashset.
- If the position is the start of a child:
- Loop over the linked hashset of started parents
- If the parent was started before the child, add the index pair to the answers.
- Break out of the loop, the remaining started parents were started after the child.
The key element that makes this fast is the following properties of a linked hashset:
- Adding an item is
O(1)
- Removing an item is
O(1)
- The insertion order of items is preserved
The last point is especially important,
combined with the idea that we are looping over positions from left to right,
so we have the ordering that we need to eliminate parent-child pairs that won't be part of the answer.
The step of looping over interesting positions above is a bit tricky.
Here's one way to do it:
- Define a new class to use for sorting, let's call it
Tracker
. It must have:
- Position of an interesting index: the start or end of a parent or child
- A flag to indicate if this position is a start or an end
- A flag to indicate if this is a parent or a child
- The original index in the parent or child list
- Build a list of
Tracker
instances from the parent and child lists
- For each parent, add two instances, one for the start and one for the end
- For each child, add two instances, one for the start and one for the end
- Sort the list, keeping in mind that the ordering is a bit tricky:
- Must be ordered by position
- When the position is the same, then:
- The start of a parent must come before its own end
- The start of a child must come before its own end
- The start of a parent at some position X must come before the start of a child at the same position X
- The end of a child at some position X must come before the end of a parent at the same position X
Evaluating the alternative algorithm
Given input with M parents and N children,
there are M * N possible combination of pairs.
To contrast the performance of the original and the suggested algorithms,
let's also consider a case where only a small subset of parents contain only a small subset of children,
that is, let's say that on average X parents contain Y children.
The original code will perform M * N comparisons, most of them will not be part of the answer.
The suggested alternative will perform an initial search step of 2 * (M + N) items, which is a log-linear operation: O(log (M + N)).
Then the main part of the algorithm performs linear logic,
generating the X * Y pairs with constant overhead: O(M + N).
The linked hashset makes this possible.
When X * Y is very close to M * N,
the overhead of the alternative algorithm may outweigh the benefits it brings.
However, the overhead grows log-linearly with M + N,
which is significantly smaller than M * N.
In other words, for large values of M and N and a uniformly random distribution of X and Y, the alternative algorithm will perform significantly better on average.
Ordering of the pairs in the answer
I want to point out that the question doesn't specify the ordering of pairs in the answers.
If a specific ordering is required,
it should be easy to modify the algorithm accordingly.
Alternative implementation
Here's an implementation of the ideas above,
and assuming that the pairs in the answer can be in any order.
List<List<Integer>> findPositions(List<List<Integer>> parent, List<List<Integer>> child) {
List<Tracker> items = new ArrayList<>();
// add the intervals with their original indexes from parent, and the parent flag set to true
for (int index = 0; index < parent.size(); index++) {
List<Integer> item = parent.get(index);
items.add(new Tracker(item.get(0), true, index, true));
items.add(new Tracker(item.get(1), false, index, true));
}
// add the intervals with their original indexes from child, and the parent flag set to false
for (int index = 0; index < child.size(); index++) {
List<Integer> item = child.get(index);
items.add(new Tracker(item.get(0), true, index, false));
items.add(new Tracker(item.get(1), false, index, false));
}
// sort the items by their position,
// parent start before child start,
// child end before parent end,
// start before end of child/parent
items.sort(Comparator.<Tracker>comparingInt(tracker -> tracker.position)
.thenComparing((a, b) -> {
if (a.isStart) {
if (b.isStart) return a.isParent ? -1 : 1;
return -1;
}
if (b.isStart) return 1;
return a.isParent ? 1 : -1;
}));
// prepare the list where we will store the answers
List<List<Integer>> answer = new ArrayList<>();
// track the parents that are started, in their insertion order
LinkedHashSet<Integer> startedParents = new LinkedHashSet<>();
// process the items one by one from left to right
for (Tracker item : items) {
if (item.isParent) {
if (item.isStart) startedParents.add(item.index);
else startedParents.remove(item.index);
} else {
if (!item.isStart) {
int childStart = child.get(item.index).get(0);
for (int parentIndex : startedParents) {
int parentStart = parent.get(parentIndex).get(0);
if (parentStart <= childStart) {
answer.add(Arrays.asList(parentIndex, item.index));
} else {
break;
}
}
}
}
}
return answer;
}
private static class Tracker {
final int position;
final boolean isStart;
final int index;
final boolean isParent;
Tracker(int position, boolean isStart, int index, boolean isParent) {
this.position = position;
this.isStart = isStart;
this.index = index;
this.isParent = isParent;
}
}