LeetCode 632 - Smallest Range
Shortest range covering all lists - PrismoSkills
Problem: Given N sorted lists, find smallest range of elements which represent one element from each list.
Solution:
While this looks simple enough, note that currently Java does not provide a Double-Ended-Priority-Queue.
So we cannot get the minimum and maximum with just one heap.
This is fixed by creating two heaps - min and max heap.
During removing, minimum element is removed from min-heap but from max-heap, element is removed by reference.
To improve performance, every integer data from the lists is wrapped into an object which also stores the line-number of the list.
With this scheme, it is easy to load the next number from that list whose element is polled from the min-heap.
public class ShortestRangeCoveringAllLists
{
// Wrapper class to store line-number along with the integer data in the Heap
public static class DataAndLineNum
{
public int data;
public int lineNum;
}
// Ascending Comparator
public static class DateLineComparator implements Comparator<DataAndLineNum>
{
@Override
public int compare(DataAndLineNum o1, DataAndLineNum o2)
{
return o1.data - o2.data;
}
}
// Descending comparator
public static class DateLineReverseComparator implements Comparator<DataAndLineNum>
{
@Override
public int compare(DataAndLineNum o1, DataAndLineNum o2)
{
return o2.data - o1.data;
}
}
public static void main(String[] args)
{
// Get some random input data
int numLists = 5;
int listSize = 4;
Object lists[] = getRandomSortedLists (numLists, listSize);
// Create ascending and descending comparators
DateLineComparator cmp = new DateLineComparator ();
DateLineReverseComparator revCmp = new DateLineReverseComparator();
// Create min-Heap and max-Heap by using PriorityQueue
PriorityQueue <DataAndLineNum> minPQ = new PriorityQueue<DataAndLineNum>(numLists, cmp);
PriorityQueue <DataAndLineNum> maxPQ = new PriorityQueue<DataAndLineNum>(numLists, revCmp);
// Put first element of each list into min-Heap and max-Heap
// Each element is converted from normal integer to wrapper class DataAndLineNum before inserting
for (int i=0; i<numLists; i++)
{
ArrayList<Integer> lst = (ArrayList<Integer>) lists[i];
DataAndLineNum info = new DataAndLineNum();
info.data = lst.get(0);
info.lineNum = i;
minPQ.add(info);
maxPQ.add(info);
}
// Heaps are initialized with first element of each list.
// Now, remove one element from min-Heap and remove corresponding element from max-Heap
// From the removed element, get the line number
// From the line-number, go directly to the list and take the next element from it.
int minDiff = 0;
while (minPQ.size() > 0)
{
if (minPQ.size() == numLists)
{
int diff = maxPQ.peek().data - minPQ.peek().data;
if (minDiff == 0 || diff < minDiff)
{
minDiff = diff;
printCurrentPQ (minPQ, minDiff);
}
}
DataAndLineNum smallest = minPQ.poll(); // remove smallest from min-Heap
maxPQ.remove(smallest); // remove same element from max-Heap
// get next number from the line whose element is removed above
int lineNum = smallest.lineNum;
ArrayList<Integer> list = (ArrayList<Integer>) lists[lineNum];
list.remove(0);
if (list.size() > 0)
{
smallest.data = list.get(0); // re-use the wrapper object smallest
minPQ.add(smallest);
maxPQ.add(smallest);
}
}
}
// Helper method to print the priority queue
static void printCurrentPQ(PriorityQueue<DataAndLineNum> pq, int minDiff)
{
System.out.print("Diff = " + minDiff);
System.out.println();
DataAndLineNum []arr = new DataAndLineNum[pq.size()];
int i=0;
while (pq.size() > 0)
{
arr[i++] = pq.poll();
System.out.println(arr[i-1].data + "(Line " + arr[i-1].lineNum + ")");
}
for (DataAndLineNum d : arr)
pq.add(d);
System.out.println();
}
There is no need for two priority queues, minPQ is enough.
The maxPQ usage also slows down the solution asymptotically
because maxPQ.remove is linear.
https://github.com/mnorbi/algo-practice/blob/master/prismo/ShortestRangeCoveringAllLists.java
static int[] solve(int[][] mat){
Comparator<Cell> comp = (c1, c2) -> Integer.compare(mat[c1.row][c1.col], mat[c2.row][c2.col]);
Cell rangeStart = findRangeStart(mat, comp);
return createRange(mat, comp, rangeStart);
}
private static Cell findRangeStart(int[][] mat, Comparator<Cell> comp) {
int ROWS = mat.length;
int COLS = mat[0].length;
PriorityQueue<Cell> minPq = new PriorityQueue<>(comp);
minPq.add(new Cell(0, 0));
Cell max = minPq.peek();
for(int i = 1; i < ROWS; ++i){
Cell cell = new Cell(i, 0);
minPq.add(cell);
if (comp.compare(max, cell) < 0){
max = cell;
}
}
Cell ret = null;
int widthOpt = Integer.MAX_VALUE;
while(true){
Cell min = minPq.remove();
int width = mat[max.row][max.col]-mat[min.row][min.col];
if (widthOpt > width){
widthOpt = width;
ret = min;
}
if (min.col == COLS-1) break;
Cell cell = new Cell(min.row, min.col +1);
minPq.add(cell);
if (comp.compare(max, cell) < 0){
max = cell;
}
}
return ret;
}
private static int[] createRange(int[][] mat, Comparator<Cell> comp, Cell rangeStart) {
int ROWS = mat.length;
int[] ret = new int[ROWS];
PriorityQueue<Cell> minPq = new PriorityQueue<>(comp);
for(int i = 0; i < ROWS; ++i){
Cell cell = new Cell(i, 0);
minPq.add(cell);
}
for(int i = 0;;){
Cell min = minPq.peek();
if (min.row == rangeStart.row && min.col == rangeStart.col){
while(!minPq.isEmpty() ){
Cell cell = minPq.remove();
ret[i++] = mat[cell.row][cell.col];
}
return ret;
}
minPq.remove();
Cell cell = new Cell(min.row, min.col+1);
minPq.add(cell);
}
}
class Cell {
int row, col;
Cell(int row, int col){
this.row = row;
this.col = col;
}
}
http://www.fgdsb.com/2015/01/17/smallest-range/
https://gist.github.com/elderdigiuseppe/6224350
http://algorithms.tutorialhorizon.com/shortest-range-in-k-sorted-lists/
Related: Find the smallest subarray covering all values - EPI
Read full article from Shortest range covering all lists - PrismoSkills
Shortest range covering all lists - PrismoSkills
Problem: Given N sorted lists, find smallest range of elements which represent one element from each list.
Solution:
- We start by creating a min-heap and a max-heap of the first elements of each list.
- In a loop,
- One element from the min-heap is removed and
- Next element from the corresponding list is put into the heap.
- At every step in the loop, min and max in the heap are analyzed.
- If their difference is lesser than the current minimum, then a new minimum is selected.
While this looks simple enough, note that currently Java does not provide a Double-Ended-Priority-Queue.
So we cannot get the minimum and maximum with just one heap.
This is fixed by creating two heaps - min and max heap.
During removing, minimum element is removed from min-heap but from max-heap, element is removed by reference.
To improve performance, every integer data from the lists is wrapped into an object which also stores the line-number of the list.
With this scheme, it is easy to load the next number from that list whose element is polled from the min-heap.
public class ShortestRangeCoveringAllLists
{
// Wrapper class to store line-number along with the integer data in the Heap
public static class DataAndLineNum
{
public int data;
public int lineNum;
}
// Ascending Comparator
public static class DateLineComparator implements Comparator<DataAndLineNum>
{
@Override
public int compare(DataAndLineNum o1, DataAndLineNum o2)
{
return o1.data - o2.data;
}
}
// Descending comparator
public static class DateLineReverseComparator implements Comparator<DataAndLineNum>
{
@Override
public int compare(DataAndLineNum o1, DataAndLineNum o2)
{
return o2.data - o1.data;
}
}
public static void main(String[] args)
{
// Get some random input data
int numLists = 5;
int listSize = 4;
Object lists[] = getRandomSortedLists (numLists, listSize);
// Create ascending and descending comparators
DateLineComparator cmp = new DateLineComparator ();
DateLineReverseComparator revCmp = new DateLineReverseComparator();
// Create min-Heap and max-Heap by using PriorityQueue
PriorityQueue <DataAndLineNum> minPQ = new PriorityQueue<DataAndLineNum>(numLists, cmp);
PriorityQueue <DataAndLineNum> maxPQ = new PriorityQueue<DataAndLineNum>(numLists, revCmp);
// Put first element of each list into min-Heap and max-Heap
// Each element is converted from normal integer to wrapper class DataAndLineNum before inserting
for (int i=0; i<numLists; i++)
{
ArrayList<Integer> lst = (ArrayList<Integer>) lists[i];
DataAndLineNum info = new DataAndLineNum();
info.data = lst.get(0);
info.lineNum = i;
minPQ.add(info);
maxPQ.add(info);
}
// Heaps are initialized with first element of each list.
// Now, remove one element from min-Heap and remove corresponding element from max-Heap
// From the removed element, get the line number
// From the line-number, go directly to the list and take the next element from it.
int minDiff = 0;
while (minPQ.size() > 0)
{
if (minPQ.size() == numLists)
{
int diff = maxPQ.peek().data - minPQ.peek().data;
if (minDiff == 0 || diff < minDiff)
{
minDiff = diff;
printCurrentPQ (minPQ, minDiff);
}
}
DataAndLineNum smallest = minPQ.poll(); // remove smallest from min-Heap
maxPQ.remove(smallest); // remove same element from max-Heap
// get next number from the line whose element is removed above
int lineNum = smallest.lineNum;
ArrayList<Integer> list = (ArrayList<Integer>) lists[lineNum];
list.remove(0);
if (list.size() > 0)
{
smallest.data = list.get(0); // re-use the wrapper object smallest
minPQ.add(smallest);
maxPQ.add(smallest);
}
}
}
// Helper method to print the priority queue
static void printCurrentPQ(PriorityQueue<DataAndLineNum> pq, int minDiff)
{
System.out.print("Diff = " + minDiff);
System.out.println();
DataAndLineNum []arr = new DataAndLineNum[pq.size()];
int i=0;
while (pq.size() > 0)
{
arr[i++] = pq.poll();
System.out.println(arr[i-1].data + "(Line " + arr[i-1].lineNum + ")");
}
for (DataAndLineNum d : arr)
pq.add(d);
System.out.println();
}
There is no need for two priority queues, minPQ is enough.
The maxPQ usage also slows down the solution asymptotically
because maxPQ.remove is linear.
https://github.com/mnorbi/algo-practice/blob/master/prismo/ShortestRangeCoveringAllLists.java
static int[] solve(int[][] mat){
Comparator<Cell> comp = (c1, c2) -> Integer.compare(mat[c1.row][c1.col], mat[c2.row][c2.col]);
Cell rangeStart = findRangeStart(mat, comp);
return createRange(mat, comp, rangeStart);
}
private static Cell findRangeStart(int[][] mat, Comparator<Cell> comp) {
int ROWS = mat.length;
int COLS = mat[0].length;
PriorityQueue<Cell> minPq = new PriorityQueue<>(comp);
minPq.add(new Cell(0, 0));
Cell max = minPq.peek();
for(int i = 1; i < ROWS; ++i){
Cell cell = new Cell(i, 0);
minPq.add(cell);
if (comp.compare(max, cell) < 0){
max = cell;
}
}
Cell ret = null;
int widthOpt = Integer.MAX_VALUE;
while(true){
Cell min = minPq.remove();
int width = mat[max.row][max.col]-mat[min.row][min.col];
if (widthOpt > width){
widthOpt = width;
ret = min;
}
if (min.col == COLS-1) break;
Cell cell = new Cell(min.row, min.col +1);
minPq.add(cell);
if (comp.compare(max, cell) < 0){
max = cell;
}
}
return ret;
}
private static int[] createRange(int[][] mat, Comparator<Cell> comp, Cell rangeStart) {
int ROWS = mat.length;
int[] ret = new int[ROWS];
PriorityQueue<Cell> minPq = new PriorityQueue<>(comp);
for(int i = 0; i < ROWS; ++i){
Cell cell = new Cell(i, 0);
minPq.add(cell);
}
for(int i = 0;;){
Cell min = minPq.peek();
if (min.row == rangeStart.row && min.col == rangeStart.col){
while(!minPq.isEmpty() ){
Cell cell = minPq.remove();
ret[i++] = mat[cell.row][cell.col];
}
return ret;
}
minPq.remove();
Cell cell = new Cell(min.row, min.col+1);
minPq.add(cell);
}
}
class Cell {
int row, col;
Cell(int row, int col){
this.row = row;
this.col = col;
}
}
http://www.fgdsb.com/2015/01/17/smallest-range/
You have k lists of sorted integers. Find the smallest range that includes at least one number from each of the k lists.For example,
List 1:[4, 10, 15, 24, 26]
List 2:[0, 9, 12, 20]
List 3:[5, 18, 22, 30]The smallest range here would be[20, 24]as it contains 24 from list 1, 20 from list 2, and 22 from list 3.
G家题,实际上就是k路归并的变种题。维护一个长度为n的min heap(n为数组个数),每次找一个最小的,同时保持记录一个最大的。
比如那个例子:
heap初始化为
pop掉最小的再push,
继续,
继续,
继续,
继续,
继续,
继续,
继续,
依次类推,最小范围为
比如那个例子:
heap初始化为
[0,4,5],min = 0, max = 5,当前最小range为[0,5]。pop掉最小的再push,
[4,5,9],min = 4, max = 9,当前最小range还是[0,5]。继续,
[5,9,10],min = 5, max = 10,当前最小range还是[0,5]。继续,
[9,10,18],min = 9, max = 18,当前最小range还是[0,5]。继续,
[10,12,18],min = 10, max = 18,当前最小range还是[0,5]。继续,
[12,15,18],min = 12, max = 18,当前最小range还是[0,5]。继续,
[15,18,20],min = 15, max = 20,当前最小range还是[0,5]。继续,
[18,20,24],min = 18, max = 24,当前最小range还是[0,5]。继续,
[20,22,24],min = 20, max = 24,当前最小range变成[20,24]。依次类推,最小范围为
[20,24]。
|
void findSmallestRange(int arr[][N], int k){ // Create a min heap with k heap nodes. Every heap node // has first element of an list int range = INT_MAX; int min = INT_MAX, max = INT_MIN; int start, end; MinHeapNode *harr = new MinHeapNode[k]; for (int i = 0; i < k; i++) { harr[i].element = arr[i][0]; // Store the first element harr[i].i = i; // index of list harr[i].j = 1; // Index of next element to be stored // from list // store max element if (harr[i].element > max) max = harr[i].element; } MinHeap hp(harr, k); // Create the heap // Now one by one get the minimum element from min // heap and replace it with next element of its list while (1) { // Get the minimum element and store it in output MinHeapNode root = hp.getMin(); // update min min = hp.getMin().element; // update range if (range > max - min + 1) { range = max - min + 1; start = min; end = max; } // Find the next element that will replace current // root of heap. The next element belongs to same // list as the current root. if (root.j < N) { root.element = arr[root.i][root.j]; root.j += 1; // update max element if (root.element > max) max = root.element; } // break if we have reached end of any list else break; // Replace root with next element of list hp.replaceMin(root); } cout << "The smallest range is " << "[" << start << " " << end << "]" << endl;;}http://algorithms.tutorialhorizon.com/shortest-range-in-k-sorted-lists/
Related: Find the smallest subarray covering all values - EPI
Read full article from Shortest range covering all lists - PrismoSkills