Google Interview Question
Software Engineer / DevelopersCountry: United States
Interview Type: Phone Interview
Very elegant solution!. Just one caveat.. In the case there there are 2 or more intervals sharing the same start or end values i.e a.start == b.start || a.end == b.end || a.start == b.end || a.end == b.start. In this case you would need to update the value for the key rather than just put it.
Must do something like this...
if (times.contains(timeValue)) {
long oldVal = times.get(timeValue)
long newVal = oldVal + (or -) entry.ram
times.put(timeValue, newVal)
}
Sort the set of log entries by start time.
Remove the earliest start time from that set and add to a priority queue that orders by end time.
Compare the earliest time entry of the set from the queue, and add or remove and update the ram usage.
Finish when set is empty.
Running time: O(n lg(n))
I haven't yet understood the approach. Can you please use an example to demonstrate the usage of your approach? This will be helpful!
Okay I will trace an example for you.
Given the a set of logs in the form of (start time, end time, ram usage):
1. 0, 100, 50
2. 50, 250, 1000
3. 100, 125, 240
4. 150, 175, 300
First you must sort them by start time, in this case it is already sorted, but the question does not guarantee that it does.
We will initially fill the priority queue with the first job.
Set: <2,3,4> PQ:<1>
We update the current ram usage: 50.
We compare it with the highest ram usage: Initially zero
and update it: 50.
Now we decide whether to remove the job from the PQ or insert another item into the PQ from the ordered set. This is determined by if the start time of the next job to be inserted (Job 2) is less or more than the top job of the PQ's end time (Job 1). In layman terms which will occur first, Job 1 ending or Job 2 starting. In this case job 2 starts before job 1 ends.
Additional note: In the corner case where one job ends as another starts which job do we pick? In real life this probably won't occur as the times are atomically defined (and depends on other factors like kernel design and parallel processing), but if the times were truncated we can have this situation occurring. Make sure you handle this case.
Set: <3,4> PQ:<1,2>
Update current ram: 1050
Update max ram: 1050 (was 50)
Now we compare job 3 start time with job 1 end time.
They are the same time. To handle this I can either pick one undeterministically (randomly) or define that either jobs always start before another one ends or vice versa. To make things simple I pick the latter.
Set:<4> PQ:<1,3,2>
Update current ram: 1290
Update max ram: 1290 (was 1050)
Now we compare job 4 start time with job 1 end time.
Job 1 ends before job 4 starts.
Set:<4> PQ:<3,2>
Update current ram: 1240
Update max ram: 1290 (unchanged when removing jobs)
Now we compare job 4 start time with job 3 end time.
Job 3 ends before job 4 starts.
Set:<4> PQ:<2>
Update current ram: 1000
Update max ram: 1290 (unchanged when removing jobs)
Now we compare job 4 start time with job 2 end time.
Job 4 starts before job 2 ends.
Set:<> PQ:<4,2>
Update current ram: 1300
Update max ram: 1300 (was 1290)
The set is now empty, no more jobs to add so we know that ram usage cannot increase any further.
Return the max ram.
Sort: O(n lg(n))
Delete from head of set at n times: n
Insert to priority queue n times : n lg(n)
Remove from priority queue at most n times: n lg(n)
Comparisons: 2n
Update current ram: 2n
Update max ram: n
This looks like interval problem. Using interval tree we should be able to find the intersecting or overlapping intervals. peakRAM would be maximum intersection in given input collection.
Could you explain this more? Assuming I am understanding you correctly, wouldn't the maximum intersection mean the maximum number of jobs running at a single moment - a good heuristic, but the number of jobs doesn't necessarily equate to large ram usage (counterexample is one single large program).
import java.util.*;
class logEntries{
int startTime;
int endTime;
int RAMUsage;
int id;
logEntries(int startTime,int endTime, int RAMUsage,int id)
{
this.startTime=startTime;this.endTime=endTime;
this.RAMUsage=RAMUsage;this.id=id;
}
}
class entryNode
{
int id;
int time;
int RAMUsage;
entryNode(int id, int time, int RAMUsage)
{
this.id=id;
this.time=time;
this.RAMUsage=RAMUsage;
}
}
public class peakRAM {
public static Comparator<entryNode> entryComparator=new Comparator<entryNode>(){
public int compare(entryNode n1, entryNode n2)
{
return (int)(Math.abs(n1.time)-Math.abs(n2.time));
}
};
static int findPeakRam(ArrayList<logEntries> entries)
{
Queue<entryNode> entryPriorityQueue=new PriorityQueue<entryNode>(1,entryComparator);
//Dividing the entries t
for(logEntries en : entries)
{
entryPriorityQueue.add(new entryNode(en.id,en.startTime,en.RAMUsage));
entryPriorityQueue.add(new entryNode(en.id,-en.endTime,en.RAMUsage));
}
//Polling the entries
int Max=0,curr=0;
while(true)
{
entryNode en=entryPriorityQueue.poll();
if(en==null)
break;
//System.out.println("ID: "+en.id + " time : "+en.time + " RAMUsage : "+en.RAMUsage);
if(en.time < 0)//
curr-=en.RAMUsage;
else
{
curr+=en.RAMUsage;
Max=Math.max(curr, Max);
}
}
return Max;
}
public static void main(String[] args) {
ArrayList<logEntries> entries=new ArrayList<logEntries>();
entries.add(new logEntries(0, 100, 500, 1));
entries.add(new logEntries(10, 70, 300, 2));
entries.add(new logEntries(50, 200, 1000, 3));
entries.add(new logEntries(110, 220, 400, 4));
entries.add(new logEntries(175, 280, 750, 5));
System.out.println(findPeakRam(entries));
}
}
/*
public class LogEntry {
public final long startTime; // start time of a job in millisec granularity
public final long endTime; // end time of a job in millisec granularity.
public final long ram; // the amount of ram the job occupies.
public final long jobId;
... constructor ...
}
running total of RAM
|
| 3GB
| -----
| 2GB
| ------
| 1GB -----------
|----- -----------
|
|____________________________________________________time
Find the peakRAM when the input is a collection of LogEntry objects
for visual c++ 2005
*/
#include "stdafx.h"
#include <iostream>
#include <conio.h>
#include <algorithm>
#include <map>
#include <vector>
#include <list>
#include <iterator>
#include <math.h>
#include <numeric>
#include <sstream>
#include <stack>
#include <string>
using namespace std;
struct LogEntry
{
long startTime;
long endTime;
long ram;
long jobId;
LogEntry( long startTime_, long endTime_, long ram_,long jobId_)
{
startTime = startTime_;
endTime = endTime_;
ram = ram_;
jobId = jobId_;
}
};
struct MemEntry
{
long nTime;
long nMemAdd;
MemEntry( long nTime_, long nMemAdd_ )
{
nTime = nTime_;
nMemAdd = nMemAdd_;
}
};
bool CompareFunc (MemEntry i,MemEntry j)
{
return (i.nTime < j.nTime);
}
class Solution
{
public:
static long CalcPeakMemory( vector<LogEntry> vecLogs )
{
vector<MemEntry> vecMem;
for ( vector<LogEntry>::iterator it = vecLogs.begin(); it != vecLogs.end(); it ++ )
{
vecMem.push_back(MemEntry(it->startTime, it->ram));
vecMem.push_back(MemEntry(it->endTime, -it->ram));
}
sort(vecMem.begin(), vecMem.end(), CompareFunc);
long nResult = 0;
long nResultTemp = 0;
for ( vector<MemEntry>::iterator it2 = vecMem.begin(); it2 != vecMem.end(); it2 ++)
{
nResultTemp += it2->nMemAdd;
nResult = max( nResult, nResultTemp );
}
return nResult;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
vector<LogEntry> vecLogs;
vecLogs.push_back(LogEntry( 0, 10, 15, 0));
vecLogs.push_back(LogEntry( 0, 20, 30, 1));
vecLogs.push_back(LogEntry( 15, 100, 50, 2));
vecLogs.push_back(LogEntry( 50, 60, 20, 3));
vecLogs.push_back(LogEntry( 100, 110, 5, 4));
long nResult = Solution::CalcPeakMemory(vecLogs);
cout << "The peakRAM is " << nResult << "." << endl;
_getch();
return 0;
}
Use a treemap to keep a chronologically sorted set of events that map to the amount of RAM created or destroyed. Iterate over the keys to keep a running total of RAM used while keeping track of the highest amount.
- Brendan May 05, 2014