Given a set of intervals, for each of the interval i, check if there exists an interval j whose start point is bigger than or equal to the end point of the interval i, which can be called that j is on the "right" of i.
For any interval i, you need to store the minimum interval j's index, which means that the interval j has the minimum start point to build the "right" relationship for interval i. If the interval j doesn't exist, store -1 for the interval i. Finally, you need output the stored value of each interval as an array.
Note:
- You may assume the interval's end point is always bigger than its start point.
- You may assume none of these intervals have the same start point.
Example 1:
Input: [ [1,2] ] Output: [-1] Explanation: There is only one interval in the collection, so it outputs -1.Example 2:
Input: [ [3,4], [2,3], [1,2] ] Output: [-1, 0, 1] Explanation: There is no satisfied "right" interval for [3,4]. For [2,3], the interval [3,4] has minimum-"right" start point; For [1,2], the interval [2,3] has minimum-"right" start point.Example 3:
Input: [ [1,4], [2,3], [3,4] ] Output: [-1, 2, -1] Explanation: There is no satisfied "right" interval for [1,4] and [3,4]. For [2,3], the interval [3,4] has minimum-"right" start point.
Given a set of intervals, for each of the interval , check if there exists an interval whose start point is bigger than or equal to the end point of the interval , which can be called that is on the "right" of .
For any interval , you need to store the minimum interval 's index, which means that the interval has the minimum start point to build the "right" relationship for interval . If the interval doesn't exist, store -1 for the interval . Finally, you need output the stored value of each interval as an array.
The simplest solution consists of picking up every interval in the set and looking for the the interval whose start point is larger(by a minimum difference) than the chosen interval's end point by scanning the complete set for every interval chosen. While scanning, we keep a track of the interval with the minimum start point satisfying the given criteria along with its index. The result obtained for every interval chosen is stored at the corresponding index in the res array which is returned at the end.
Java
/** * Definition for an interval. * public class Interval { * int start; * int end; * Interval() { start = 0; end = 0; } * Interval(int s, int e) { start = s; end = e; } * } */ public class Solution { public int[] findRightInterval(Interval[] intervals) { int[] res = new int[intervals.length]; for (int i = 0; i < intervals.length; i++) { int min = Integer.MAX_VALUE; int minindex = -1; for (int j = 0; j < intervals.length; j++) { if (intervals[j].start >= intervals[i].end && intervals[j].start < min) { min = intervals[j].start; minindex = j; } } res[i] = minindex; } return res; } }
Complexity Analysis
We make use of a hashmap , which stores the data in the form of a pair. Here, the corresponds to the interval chosen and the corresponds to the index of the particular interval in the given array. We store every element of the array in the -map.
Now, we sort the array based on the starting points. We needed to store the indices of the array in the hashmap, so as to be able to obtain the indices even after the sorting.
Now, we pick up every interval of the sorted array, and find out the interval from the remaining ones whose start point comes just after the end point of the interval chosen. How do we proceed? Say, we've picked up the interval right now. In order to find an interval satisfying the given criteria, we need not search in the intervals behind it. This is because the array has been sorted based on the starting points and the end point is always greater than the starting point for a given interval. Thus, we search in the intervals only with indices , such that . The first element encountered while scanning in the ascending order is the required result for the interval chosen, since all the intervals lying after this interval will have comparatively larger start points.
Then, we can obtain the index corresponding to the corresponding interval from the hashmap, which is stored in the corresponding entry of the array. If no interval satifies the criteria, we put a in the corresponding entry.
Java
/** * Definition for an interval. * public class Interval { * int start; * int end; * Interval() { start = 0; end = 0; } * Interval(int s, int e) { start = s; end = e; } * } */ public class Solution { public int[] findRightInterval(Interval[] intervals) { int[] res = new int[intervals.length]; HashMap<Interval, Integer> hash = new HashMap<>(); for (int i = 0; i < intervals.length; i++) { hash.put(intervals[i], i); } Arrays.sort(intervals, (a, b) -> a.start - b.start); for (int i = 0; i < intervals.length; i++) { int min = Integer.MAX_VALUE; int minindex = -1; for (int j = i + 1; j < intervals.length; j++) { if (intervals[j].start >= intervals[i].end && intervals[j].start < min) { min = intervals[j].start; minindex = hash.get(intervals[j]); } } res[hash.get(intervals[i])] = minindex; } return res; } }
Complexity Analysis
Sorting takes time. For the first interval we need to search among elements. For the second interval, the search is done among elements and so on leading to a total of:
calculations.
We can optimize the above approach to some extent, since we can make use of the factor of the array being sorted. Instead of searching for the required interval in a linear manner, we can make use of Binary Search to find an interval whose start point is just larger than the end point of the current interval.
Again, if such an interval is found, we obtain its index from the hashmap and store the result in the appropriate entry. If not, we put a at the corresponding entry.
Java
/** * Definition for an interval. * public class Interval { * int start; * int end; * Interval() { start = 0; end = 0; } * Interval(int s, int e) { start = s; end = e; } * } */ public class Solution { public Interval binary_search(Interval[] intervals, int target, int start, int end) { if (start >= end) { if (intervals[start].start >= target) { return intervals[start]; } return null; } int mid = (start + end) / 2; if (intervals[mid].start < target) { return binary_search(intervals, target, mid + 1, end); } else { return binary_search(intervals, target, start, mid); } } public int[] findRightInterval(Interval[] intervals) { int[] res = new int[intervals.length]; HashMap<Interval, Integer> hash = new HashMap<>(); for (int i = 0; i < intervals.length; i++) { hash.put(intervals[i], i); } Arrays.sort(intervals, (a, b) -> a.start - b.start); for (int i = 0; i < intervals.length; i++) { Interval interval = binary_search(intervals, intervals[i].end, 0, intervals.length - 1); res[hash.get(intervals[i])] = interval == null ? -1 : hash.get(interval); } return res; } }
Complexity Analysis
Time complexity : . Sorting takes time. Binary search takes n$$ intervals.
Space complexity : . array of size is used. A hashmap of size is used.
In this approach, instead of using a hashmap, we make use of a TreeMap , which is simply a Red-Black Tree(a kind of balanced Binary Search Tree) . This Treemap stores data in the form of pair and always remain sorted based on its keys. In our case, we store the data such that the start point of an interval acts as the and the index corresponding to the interval acts as the value, since we are concerned with data sorted based on the start points, as discussed in previous approaches. Every element of the array is stored in the TreeMap.
Now, we choose each element of the array and make use of a function TreeMap.ceilingEntry(end_point)
to obtain the element in the TreeMap with its just larger than the of the currently chosen interval. The function ceilingEntry(Key)
returns the element just with its larger than the Key
(passed as the argument) from amongst the elements of the TreeMap and returns null
if no such element exists.
If non-null value is returned, we obtain the from the pair obtained at the appropriate entry in the array. If a null value is returned, we simply store a at the corresponding entry.
Java
/** * Definition for an interval. * public class Interval { * int start; * int end; * Interval() { start = 0; end = 0; } * Interval(int s, int e) { start = s; end = e; } * } */ public class Solution { public int[] findRightInterval(Interval[] intervals) { TreeMap<Integer, Integer> starts = new TreeMap<>(); int res[] = new int[intervals.length]; for (int i = 0; i < intervals.length; i++) { starts.put(intervals[i].start, i); } for (int i = 0; i < intervals.length; i++) { Map.Entry<Integer, Integer> pos = starts.ceilingEntry(intervals[i].end); res[i] = pos == null ? -1 : pos.getValue(); } return res; } }
Complexity Analysis
Time complexity : . Inserting an element into TreeMap takes time. such insertions are done. The search in TreeMap using ceilingEntry
also takes time. such searches are done.
Space complexity : . array of size is used. TreeMap of size is used.
Algorithm
The intuition behind this approach is as follows: If we maintain two arrays,
, which is sorted based on the start points.
, which is sorted based on the end points.
Once we pick up the first interval(or, say the interval) from the array, we can determine the appropriate interval satisfying the right interval criteria by scanning the intervals in array from left towards the right, since the array is sorted based on the start points. Say, the index of the element chosen from the array happens to be .
Now, when we pick up the next interval(say the interval) from the array, we need not start scanning the array from the first index. Rather, we can start off directly from the index where we left off last time in the array. This is because end point corresponding to is larger than the one corresponding to and none of the intervals from , such that , satisfies the right neighbor criteria with , and hence not with as well.
If at any moment, we reach the end of the array i.e. and no element satisfying the right interval criteria is available in the array, we put a in the corresponding entry. The same holds for all the remaining elements of the array, whose end points are even larger than the previous interval encountered.
Also we make use of a hashmap initially to preserve the indices corresponding to the intervals even after sorting.
For more understanding see the below animation:
Java
/** * Definition for an interval. * public class Interval { * int start; * int end; * Interval() { start = 0; end = 0; } * Interval(int s, int e) { start = s; end = e; } * } */ public class Solution { public int[] findRightInterval(Interval[] intervals) { Interval[] endIntervals = Arrays.copyOf(intervals, intervals.length); HashMap<Interval, Integer> hash = new HashMap<>(); for (int i = 0; i < intervals.length; i++) { hash.put(intervals[i], i); } Arrays.sort(intervals, (a, b) -> a.start - b.start); Arrays.sort(endIntervals, (a, b) -> a.end - b.end); int j = 0; int[] res = new int[intervals.length]; for (int i = 0; i < endIntervals.length; i++) { while (j < intervals.length && intervals[j].start < endIntervals[i].end) { j++; } res[hash.get(endIntervals[i])] = j == intervals.length ? -1 : hash.get(intervals[j]); } return res; } }
Complexity Analysis
Time complexity : . Sorting takes time. A total of time is spent on searching for the appropriate intervals, since the and array is scanned only once.
Space complexity : . , and array of size are used. A hashmap of size is used.
Analysis written by: @vinod23
subscribe for articles.
You have already subscribed for articles.
{[{ ac.errorInfo || "Something wrong. Please try again later."}]}
Success!