mirror of
https://github.com/dholerobin/Lecture_Notes.git
synced 2025-03-15 13:49:59 +00:00
Adds Interview Lectures
This commit is contained in:
parent
ecccc5c6fd
commit
721bfdda17
457
Amazon and Microsoft Interview/1.md
Normal file
457
Amazon and Microsoft Interview/1.md
Normal file
@ -0,0 +1,457 @@
|
||||
# Amazon and Microsoft Interviews
|
||||
## Interview Tips
|
||||
1. Always give Brute Force for every question.
|
||||
- Benefits:
|
||||
- It favours against worse odds. It is better to give a brute force then to be totally blank.
|
||||
- Predicts Optimal Solution
|
||||
2. Data Structures/ Algorithms
|
||||
|
||||
Time Complexity| Datastructure and Algorithms
|
||||
:--|:--
|
||||
$O(n\log{n})$ | Sorting
|
||||
$O(n)$ | Two Pointers/ Smart Sorting/ dfs/ bfs
|
||||
$O(n^2)$ | Dynamic Programming
|
||||
$O(\log{n})$ | Binary Search
|
||||
$O(1)$ | HashMap
|
||||
|
||||
3. Writing a working code
|
||||
- Google/Facebook/Uber/Microsoft
|
||||
- They need working code
|
||||
- Take care of Edge cases
|
||||
- Candidates may get rejected for not taking care of Edge cases
|
||||
- Spend $10\%$ of your time on Edge cases
|
||||
- Give proper names to characters
|
||||
- Benefits
|
||||
- Less Confusion, Error free code
|
||||
- Good presentation and easier to explain
|
||||
|
||||
## Question 1 Binary Tree Maximum Path Sum
|
||||
Given a non-empty binary tree, find the maximum path sum.
|
||||
|
||||
For this problem, a path is defined as any sequence of nodes from some starting node to any node in the tree along the parent-child connections. The path must contain at least one node and does not need to go through the root.
|
||||
|
||||
Example 1:
|
||||
|
||||
```C++
|
||||
Input: [1,2,3]
|
||||
|
||||
1
|
||||
/ \
|
||||
2 3
|
||||
|
||||
Output: 6
|
||||
```
|
||||
|
||||
Example 2:
|
||||
|
||||
```C++
|
||||
Input: [-10,9,20,null,null,15,7]
|
||||
|
||||
-10
|
||||
/ \
|
||||
9 20
|
||||
/ \
|
||||
15 7
|
||||
|
||||
Output: 42
|
||||
```
|
||||
-----
|
||||
__Brute Force:__
|
||||
- For Every Pair of node find the path sum
|
||||
- Find maximum among all the paths
|
||||
- $O(n^3)$
|
||||
|
||||
__Hints:__
|
||||
- Are all the pairs required?
|
||||
- For a tree, what are the possibilities of maximum path sum
|
||||
1. Only root
|
||||
2. Path from left subtree that ends at root
|
||||
3. Path from right subtree that ends at root
|
||||
4. Path from left to right passing through the root
|
||||
- Are these enough?
|
||||
6. Path only in left
|
||||
7. Path only in right
|
||||
- If we take care of all these paths, we can recursively find the maximum path sum.
|
||||
|
||||
__Code__:
|
||||
```C++
|
||||
/**
|
||||
* Definition for a binary tree node.
|
||||
* struct TreeNode {
|
||||
* int val;
|
||||
* TreeNode *left;
|
||||
* TreeNode *right;
|
||||
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
|
||||
* };
|
||||
*/
|
||||
class Solution {
|
||||
public:
|
||||
int global_max;
|
||||
int pathEndingAtRoot(TreeNode* root){
|
||||
if(root == NULL) return 0;
|
||||
int left = max(pathEndingAtRoot(root->left),0);
|
||||
int right = max(pathEndingAtRoot(root->right),0);
|
||||
int path_with_root = left+right+root->val;
|
||||
global_max = max(path_with_root, global_max);
|
||||
return max(left+root->val , right+root->val);
|
||||
|
||||
}
|
||||
int maxPathSum(TreeNode* root) {
|
||||
global_max = root->val;
|
||||
pathEndingAtRoot(root);
|
||||
return global_max;
|
||||
}
|
||||
};
|
||||
```
|
||||
__Time Complexity__
|
||||
- $O(n)$ as all the nodes are visited exactly once.
|
||||
|
||||
__Dry Run__
|
||||
```C++
|
||||
Input: [-10,9,20,null,null,15,7]
|
||||
|
||||
-10
|
||||
/ \
|
||||
9 20
|
||||
/ \
|
||||
15 7
|
||||
|
||||
Output: 42
|
||||
```
|
||||
|Root| Left|Right|Path_with_root| global_max|
|
||||
|:--|:--|:--|:--|:--|
|
||||
|-10|9|35|34|-10|
|
||||
|9|0|0|9|9|
|
||||
|20|15|7|42|42|
|
||||
|15|0|0|15|15|
|
||||
|7|0|0|7|15|
|
||||
|
||||
## Question 2 Find Median from Data Stream
|
||||
Median is the middle value in an ordered integer list. If the size of the list is even, there is no middle value. So the median is the mean of the two middle value.
|
||||
|
||||
For example,
|
||||
\[2,3,4\], the median is 3
|
||||
|
||||
\[2,3\], the median is (2 + 3) / 2 = 2.5
|
||||
|
||||
Design a data structure that supports the following two operations:
|
||||
|
||||
void addNum(int num) - Add a integer number from the data stream to the data structure.
|
||||
double findMedian() - Return the median of all elements so far.
|
||||
|
||||
|
||||
Example:
|
||||
```C++
|
||||
addNum(1)
|
||||
addNum(2)
|
||||
findMedian() -> 1.5
|
||||
addNum(3)
|
||||
findMedian() -> 2
|
||||
```
|
||||
-----
|
||||
__Brute Force__
|
||||
- Create a sorted array
|
||||
- Keep on adding nodes at there appropriate places just like insertion sort
|
||||
- addNum: $O(n)$
|
||||
- findMedian: $O(1)$
|
||||
|
||||
__Hints:__
|
||||
- In the above solution we do not care about the ordering in the lower half and upper half of the array.
|
||||
- All we need is that the maximum element of lower half is smaller than minimum element of the upper half.
|
||||
- Also, the sizes of the two parts are equal.
|
||||
- Can we use two heaps to solve this question?
|
||||
|
||||
__Code:__
|
||||
```C++
|
||||
class MedianFinder {
|
||||
public:
|
||||
/** initialize your data structure here. */
|
||||
priority_queue<int> max_heap_lesser_part;
|
||||
priority_queue<int, vector<int>, greater<int>> min_heap_greater_part;
|
||||
MedianFinder() {
|
||||
|
||||
}
|
||||
|
||||
void addNum(int num) {
|
||||
if(max_heap_lesser_part.size() == 0){
|
||||
max_heap_lesser_part.push(num);
|
||||
return;
|
||||
}
|
||||
if(max_heap_lesser_part.top() > num){
|
||||
max_heap_lesser_part.push(num);
|
||||
}else{
|
||||
min_heap_greater_part.push(num);
|
||||
}
|
||||
while(max_heap_lesser_part.size() > min_heap_greater_part.size() + 1){
|
||||
int temp = max_heap_lesser_part.top();
|
||||
max_heap_lesser_part.pop();
|
||||
min_heap_greater_part.push(temp);
|
||||
}
|
||||
while(min_heap_greater_part.size() > max_heap_lesser_part.size()){
|
||||
int temp = min_heap_greater_part.top();
|
||||
min_heap_greater_part.pop();
|
||||
max_heap_lesser_part.push(temp);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
double findMedian() {
|
||||
int n = max_heap_lesser_part.size() + min_heap_greater_part.size();
|
||||
if(n%2 == 0){
|
||||
return (max_heap_lesser_part.top() + min_heap_greater_part.top())/2.0;
|
||||
}else{
|
||||
return max_heap_lesser_part.top()*1.0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Your MedianFinder object will be instantiated and called as such:
|
||||
* MedianFinder* obj = new MedianFinder();
|
||||
* obj->addNum(num);
|
||||
* double param_2 = obj->findMedian();
|
||||
*/
|
||||
```
|
||||
|
||||
__Time Complexity__
|
||||
- AddNum : $O(1)$ as there can be only constant operations in the heaps.
|
||||
- FindMedian: $O(1)$ trivially.
|
||||
- Space: $O(n)$ we need to store every number.
|
||||
|
||||
__Dry Run:__
|
||||
```
|
||||
Array : [10,3,5,8,4]
|
||||
```
|
||||
|Max Heap(Lower Half)|Min Heap(Upper Half)|Median|
|
||||
|:--|:--|:--|
|
||||
|10| |10|
|
||||
|10,3| | |
|
||||
|3|10|6.5|
|
||||
|3,5|10|5|
|
||||
|3,5|8,10|6.5|
|
||||
|3,4,5|8,10|5|
|
||||
|
||||
## Question 3 Count Complete Tree Nodes
|
||||
Given a complete binary tree, count the number of nodes.
|
||||
|
||||
__Note:__
|
||||
|
||||
Definition of a complete binary tree from __Wikipedia__:
|
||||
|
||||
In a complete binary tree every level, except possibly the last, is completely filled, and all nodes in the last level are as far left as possible. It can have between 1 and 2h nodes inclusive at the last level h.
|
||||
|
||||
__Example:__
|
||||
|
||||
```C++
|
||||
Input:
|
||||
1
|
||||
/ \
|
||||
2 3
|
||||
/ \ /
|
||||
4 5 6
|
||||
|
||||
Output: 6
|
||||
```
|
||||
-----
|
||||
__Brute Force__
|
||||
- By doing any tree traversal we can find the number of nodes in the tree.
|
||||
|
||||
__Hints:__
|
||||
- This is a complete tree. Can we use this property?
|
||||
- Can we find the height of a complete binary tree in $O(\log{n})$ time?
|
||||
- All the level except the last level are full in a Complete Binary Tree.
|
||||
- What will be the total number of nodes in all the levels except the last level?
|
||||
- If the height is h, it will be $2^{h-1} - 1$
|
||||
- How many nodes will be there in the last level?
|
||||
- The nodes will belong to $[1,2^{h-1}]$.
|
||||
- All the nodes in the last level are towards left.
|
||||
- Let's number the nodes from $0$ to $2^{h-1}-1$. And look at their binary representation.
|
||||
|
||||
```C++
|
||||
X
|
||||
/ \
|
||||
X X
|
||||
/ \ / \
|
||||
X X X X
|
||||
/ \ / \ / \ / \
|
||||
0 1 2 3 4 5 6 7
|
||||
----------------
|
||||
0 0 0 0 1 1 1 1
|
||||
0 0 1 1 0 0 1 1
|
||||
0 1 0 1 0 1 0 1
|
||||
```
|
||||
- In this binary representation we can see if $0$ means left and $1$ means right, we know the path to the node.
|
||||
- Now, we know how to check if a given node is present or not. We also know that all the nodes are towards the left.
|
||||
- Can we do a binary search to find the last node that is persent?
|
||||
|
||||
__Code__:
|
||||
```C++
|
||||
/**
|
||||
* Definition for a binary tree node.
|
||||
* struct TreeNode {
|
||||
* int val;
|
||||
* TreeNode *left;
|
||||
* TreeNode *right;
|
||||
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
|
||||
* };
|
||||
*/
|
||||
class Solution {
|
||||
public:
|
||||
int getHeight(TreeNode * root){
|
||||
if(root == NULL) return 0;
|
||||
return getHeight(root->left) + 1;
|
||||
}
|
||||
bool present(TreeNode * root, int node, int h){
|
||||
if(root == NULL) return false;
|
||||
if(h == 1){
|
||||
return node == 0;
|
||||
}
|
||||
if((node>>(h-2))&1 == 1){
|
||||
int new_node = node%((int)pow(2,h-2));
|
||||
return present(root->right, new_node, h-1);
|
||||
}else{
|
||||
int new_node = node%((int)pow(2,h-2));
|
||||
return present(root->left, new_node, h-1);
|
||||
}
|
||||
}
|
||||
int countNodes(TreeNode* root) {
|
||||
if(root == NULL) return 0;
|
||||
int h = getHeight(root);
|
||||
int lo = 0, hi = pow(2,h-1) - 1;
|
||||
int ans = -1;
|
||||
while(lo<=hi){
|
||||
int mid = lo+(hi-lo)/2;
|
||||
if(present(root,mid,h)){
|
||||
ans = mid + 1;
|
||||
lo = mid+1;
|
||||
}else{
|
||||
hi = mid-1;
|
||||
}
|
||||
}
|
||||
return ans + pow(2,h-1) - 1;
|
||||
}
|
||||
};
|
||||
```
|
||||
__Dry Run__
|
||||
```C++
|
||||
Input:
|
||||
1
|
||||
/ \
|
||||
2 3
|
||||
/ \ /
|
||||
4 5 6
|
||||
--------
|
||||
0 1 2 3
|
||||
```
|
||||
|Lo|Hi|Mid|Present|Answer|
|
||||
|:--|:--|:--|:--|:--|
|
||||
|0|3|1|Yes|1|
|
||||
|2|3|2|Yes|2|
|
||||
|3|3|3|No|2|
|
||||
|3|2|Break| | |
|
||||
|
||||
__Time Complexity__
|
||||
- For every check $O(\log{n})$
|
||||
- Total number of checks $O(\log{n})$
|
||||
- Time Complexity : $O(\log^2{n})$
|
||||
|
||||
## Question 4
|
||||
In a given integer array A, we must move every element of A to either list B or list C. (B and C initially start empty.)
|
||||
|
||||
Return true if and only if after such a move, it is possible that the average value of B is equal to the average value of C, and B and C are both non-empty.
|
||||
|
||||
```C++
|
||||
Example :
|
||||
Input:
|
||||
[1,2,3,4,5,6,7,8]
|
||||
Output: true
|
||||
Explanation: We can split the array into [1,4,5,8] and [2,3,6,7],
|
||||
and both of them have the average of 4.5.
|
||||
```
|
||||
|
||||
Note:
|
||||
- The length of A will be in the range \[1, 30\].
|
||||
- A\[i\] will be in the range of \[0, 10000\].
|
||||
-----
|
||||
__Another version__
|
||||
- Find two subarrays such that their average is same?
|
||||
- Average of the whole array will be equal to the average of the two sub-arrays.
|
||||
- Run a pointer from 0 to n-1. Check if the average of nodes from zero to the pointer is equal to the average of the array.
|
||||
|
||||
__Brute Force__
|
||||
- Create all the possible subsets. Check their averages and report if found.
|
||||
- $O(2^n*n)$
|
||||
|
||||
__Hints__
|
||||
- We will be doing something similar here too. We will only be optimising a little bit.
|
||||
- Average of the whole array will be equal to the average of the two sub-arrays.
|
||||
$$\frac{S}{n} = \text{Average of the whole array}$$
|
||||
$$\frac{S_k}{k} = \text{Average of the first Sub Array}$$
|
||||
$$\frac{S-S_k}{n-k} = \text{Average of the second Sub Array}$$
|
||||
$$\text{Both the averagers are equal}$$
|
||||
$$\frac{S_k}{k} = \frac{S-S_k}{n-k}$$
|
||||
$$S_k*(n-k) = (S-S_k) * k$$
|
||||
$$S_k* n - S_k * k = S * k - S_k * k$$
|
||||
$$\frac{S_k}{k}= \frac{S}{n}$$
|
||||
|
||||
__Solution__
|
||||
First, this problem is NP, and the worst case runtime is exponential. But the expected runtime for random input could be very fast.
|
||||
|
||||
If the array of size n can be splitted into group A and B with same mean, assuming A is the smaller group, then
|
||||
|
||||
```
|
||||
totalSum/n = Asum/k = Bsum/(n-k),
|
||||
where k = A.size() and 1 <= k <= n/2;
|
||||
Asum = totalSum*k/n, which is an integer.
|
||||
So we have totalSum*k%n == 0;
|
||||
In general, not many k are valid.
|
||||
```
|
||||
|
||||
__Solution 2__: early pruning + knapsack DP, O(n^3 * M) 33 ms
|
||||
If there are still some k valid after early pruning by checking totalSum*k%n == 0,
|
||||
we can generate all possible combination sum of k numbers from the array using DP, like knapsack problem. (Note: 1 <= k <= n/2)
|
||||
Next, for each valid k, simply check whether the group sum, i.e. totalSum * k / n, exists in the kth combination sum hashset.
|
||||
|
||||
```
|
||||
vector<vector<unordered_set<int>>> sums(n, vector<unordered_set<int>>(n/2+1));
|
||||
sums[i][j] is all possible combination
|
||||
sum of j numbers from the subarray A[0, i];
|
||||
```
|
||||
__Goal__: sums$[n-1][k]$, for all k in range $[1, n/2]$
|
||||
__Initial condition__: $sums[i][0] = {0}, 0 <= i <= n-1; sums[0][1] =${all numbers in the array};
|
||||
__Deduction__: $sums[i+1][j] = sums[i][j]$ "join" $(sums[i][j-1] + A[i+1])$
|
||||
The following code uses less space but the same DP formula.
|
||||
__Runtime analysis__:
|
||||
All numbers in the array are in range [0, 10000]. Let M = 10000.
|
||||
So the size of kth combination sum hashset, i.e. sums[...][k], is <= k * M;
|
||||
For each number in the array, the code need loop through all combination sum hashsets, so
|
||||
the total runtime is n * (1 * M + 2 * M + ... + (n/2) * M) = O(n^3 * M)
|
||||
|
||||
__Code__
|
||||
|
||||
```C++
|
||||
class Solution {
|
||||
public:
|
||||
bool splitArraySameAverage(vector<int>& A) {
|
||||
int n = A.size(), m = n/2, totalSum = accumulate(A.begin(), A.end(), 0);
|
||||
// early pruning
|
||||
bool isPossible = false;
|
||||
for (int i = 1; i <= m && !isPossible; ++i)
|
||||
if (totalSum*i%n == 0) isPossible = true;
|
||||
if (!isPossible) return false;
|
||||
// DP like knapsack
|
||||
vector<unordered_set<int>> sums(m+1);
|
||||
sums[0].insert(0);
|
||||
for (int num: A) {
|
||||
for (int i = m; i >= 1; --i)
|
||||
for (const int t: sums[i-1])
|
||||
sums[i].insert(t + num);
|
||||
}
|
||||
for (int i = 1; i <= m; ++i)
|
||||
if (totalSum*i%n == 0 && sums[i].find(totalSum*i/n) != sums[i].end()) return true;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
|
479
Google and Facebook Interviews/1.md
Normal file
479
Google and Facebook Interviews/1.md
Normal file
@ -0,0 +1,479 @@
|
||||
# Google and Facebook Interviews
|
||||
## Interview Tips
|
||||
1. Always give Brute Force for every question.
|
||||
- Benefits:
|
||||
- It favours against worse odds. It is better to give a brute force then to be totally blank.
|
||||
- Predicts Optimal Solution
|
||||
2. Data Structures/ Algorithms
|
||||
|
||||
Time Complexity| Datastructure and Algorithms
|
||||
:--|:--
|
||||
$O(n\log{n})$ | Sorting
|
||||
$O(n)$ | Two Pointers/ Smart Sorting/ dfs/ bfs
|
||||
$O(n^2)$ | Dynamic Programming
|
||||
$O(\log{n})$ | Binary Search
|
||||
$O(1)$ | HashMap
|
||||
|
||||
3. Writing a working code
|
||||
- Google/Facebook/Uber/Microsoft
|
||||
- They need working code
|
||||
- Take care of Edge cases
|
||||
- Candidates may get rejected for not taking care of Edge cases
|
||||
- Spend $10\%$ of your time on Edge cases
|
||||
- Give proper names to characters
|
||||
- Benefits
|
||||
- Less Confusion, Error free code
|
||||
- Good presentation and easier to explain
|
||||
|
||||
## Question 1 Time Based Key-Value Store
|
||||
Create a timebased key-value store class TimeMap, that supports two operations.
|
||||
|
||||
1. set(string key, string value, int timestamp)
|
||||
- Stores the key and value, along with the given timestamp.
|
||||
2. get(string key, int timestamp)
|
||||
- Returns a value such that set(key, value, times tamp_prev) was called previously, with timestamp_prev <= timestamp.
|
||||
- If there are multiple such values, it returns the one with the largest timestamp_prev.
|
||||
- If there are no values, it returns the empty string ("").
|
||||
|
||||
|
||||
Example 1:
|
||||
|
||||
```C++
|
||||
Input:
|
||||
inputs = ["TimeMap","set","get","get","set","get","get"],
|
||||
inputs = [[],["foo","bar",1],["foo",1],["foo",3],
|
||||
["foo","bar2",4],["foo",4],["foo",5]]
|
||||
Output: [null,null,"bar","bar",null,"bar2","bar2"]
|
||||
Explanation:
|
||||
TimeMap kv;
|
||||
kv.set("foo", "bar", 1); // store the key "foo" and value "bar" along with timestamp = 1
|
||||
kv.get("foo", 1); // output "bar"
|
||||
kv.get("foo", 3); // output "bar" since there is no value corresponding to foo at timestamp 3 and timestamp 2, then the only value is at timestamp 1 ie "bar"
|
||||
kv.set("foo", "bar2", 4);
|
||||
kv.get("foo", 4); // output "bar2"
|
||||
kv.get("foo", 5); //output "bar2"
|
||||
```
|
||||
Example 2:
|
||||
```C++
|
||||
Input: inputs = ["TimeMap","set","set","get","get","get","get","get"], inputs = [[],["love","high",10],["love","low",20],["love",5],["love",10],["love",15],["love",20],["love",25]]
|
||||
Output: [null,null,null,"","high","high","low","low"]
|
||||
```
|
||||
|
||||
__Note:__
|
||||
- All key/value strings are lowercase.
|
||||
- All key/value strings have length in the range [1, 100]
|
||||
- The timestamps for all TimeMap.set operations are strictly increasing.
|
||||
- 1 <= timestamp <= 10^7
|
||||
- TimeMap.set and TimeMap.get functions will be called a total of 120000 times (combined) per test case.
|
||||
|
||||
__Brute Force__
|
||||
- Store key, value and timestamp in an array.
|
||||
- For every get request run a linear loop to find the largest timestamp in the given bounds with the given key. Return value.
|
||||
- Time complexity:
|
||||
- TimeMap.set = $O(1)$.
|
||||
- TimeMap.get = $O(n)$. Because you are iterating over the whole array.
|
||||
|
||||
__Hints__
|
||||
- Suppose there were no time stamps. What would be the best data structure for this problem. Hashmap, As it gives $O(1)$ look up.
|
||||
- If we store all the timestamps corresponding to a key in an array, and store that array as value in a HashMap. Then for a given key we can run a loop on the corresponding array and find our answer.
|
||||
- This approach is still $O(n)$ as we may have only one key.
|
||||
- It is given that "The timestamps for all TimeMap.set operations are strictly increasing." Can we do a binary search to find our answer?
|
||||
- The time complexity now will be $O(\log{n})$.
|
||||
|
||||
__Code__
|
||||
```C++
|
||||
class TimeMap {
|
||||
public:
|
||||
/** Initialize your data structure here. */
|
||||
unordered_map<string, vector<pair<int,string>>> mp;
|
||||
TimeMap() {
|
||||
|
||||
}
|
||||
|
||||
void set(string key, string value, int timestamp) {
|
||||
if(mp.find(key)!= mp.end()){
|
||||
mp[key].push_back({timestamp,value});
|
||||
}else{
|
||||
vector<pair<int,string>> temp;
|
||||
temp.push_back({timestamp,value});
|
||||
mp[key] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
string get(string key, int timestamp) {
|
||||
if(mp.find(key) == mp.end()){
|
||||
return NULL;
|
||||
}
|
||||
int lo = 0, hi = mp[key].size()-1;
|
||||
string ans;
|
||||
while(lo<=hi){
|
||||
int mid = lo + (hi-lo)/2;
|
||||
if(mp[key][mid].first <= timestamp){
|
||||
ans = mp[key][mid].second;
|
||||
lo = mid+1;
|
||||
}else{
|
||||
hi = mid-1;
|
||||
}
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Your TimeMap object will be instantiated and called as such:
|
||||
* TimeMap* obj = new TimeMap();
|
||||
* obj->set(key,value,timestamp);
|
||||
* string param_2 = obj->get(key,timestamp);
|
||||
*/
|
||||
```
|
||||
__Time Complexity__
|
||||
- Worst case it will $O(\log{n})$ for each get query, where n is the number of set queries.
|
||||
- For set query it will be $O(1)$.
|
||||
|
||||
__Dry Run__
|
||||
```C++
|
||||
TimeMap kv;
|
||||
kv.set("foo", "bar", 1); // store the key "foo" and value "bar" along with timestamp = 1
|
||||
kv.set("foo", "bar2", 2);
|
||||
kv.set("foo", "bar3", 3);
|
||||
kv.set("foo", "bar4", 4);
|
||||
kv.get("foo", 3);
|
||||
```
|
||||
|lo|hi|mid|key|mp[key]|Answer|
|
||||
|:--|:--|:--|:--|:--|:--|
|
||||
|0|3|1|foo|{{1,bar},{2,bar2},{3,bar3},{4,bar4}}|bar2|
|
||||
|2|3|2|foo|{{1,bar},{2,bar2},{3,bar3},{4,bar4}}|bar3|
|
||||
|3|3|3|foo|{{1,bar},{2,bar2},{3,bar3},{4,bar4}}|bar3|
|
||||
|3|2| ||||
|
||||
|
||||
## Question 2 Order of People Heights
|
||||
You are given the following :
|
||||
|
||||
A positive number N
|
||||
Heights : A list of heights of N persons standing in a queue
|
||||
Infronts : A list of numbers corresponding to each person (P) that gives the number of persons who are taller than P and standing in front of P
|
||||
You need to return list of actual order of persons’s height
|
||||
|
||||
Consider that heights will be unique
|
||||
|
||||
Example
|
||||
|
||||
```C++
|
||||
Input :
|
||||
Heights: 5 3 2 6 1 4
|
||||
InFronts: 0 1 2 0 3 2
|
||||
Output :
|
||||
actual order is: 5 3 2 1 6 4
|
||||
|
||||
Explaination:
|
||||
|
||||
So, you can see that for the person with height 5,
|
||||
there is no one taller than him who is in front of him,
|
||||
and hence Infronts has 0 for him.
|
||||
|
||||
For person with height 3, there is 1 person
|
||||
( Height : 5 ) in front of him who is taller than him.
|
||||
|
||||
You can do similar inference for other people in the list.
|
||||
```
|
||||
__Brute Force__
|
||||
- Go through all the permutations of the heights and for every permutation check if it holds true with Infronts order.
|
||||
- The time complexity of this approach will be $O(n!* n)$
|
||||
|
||||
__Hints__
|
||||
- If we sort the arrays based on heights, can we do something?
|
||||
```C++
|
||||
Heights: 1 2 3 4 5 6
|
||||
Infront: 3 2 1 2 0 0
|
||||
```
|
||||
- We know the largest element will always have $0$ infront value. No matter how many elements we put in front of largest element there will be no larger element than the largest element.
|
||||
- Lets put largest element at first place.
|
||||
```C++
|
||||
Ans: 6
|
||||
```
|
||||
- Now for the next largest element we know the position to insert.
|
||||
```C++
|
||||
Ans: 5 6
|
||||
Ans: 5 6 4
|
||||
Ans: 5 3 6 4
|
||||
Ans: 5 3 2 6 4
|
||||
Ans: 5 3 2 1 6 4
|
||||
```
|
||||
- Putting a smaller element in front a given element will not affect it. Thus if we move from the largest to smallest we can insert elements at their respective positions.
|
||||
- Time complexity: $O(n^2)$. As we are shifting all the elements while inserting a given element. This is similar to insertion sort.
|
||||
- Can we do better than this?
|
||||
- If we already know which place a given element should go, we would not have to move the elements.
|
||||
- For this we need to know the number of empty places infront a given position.
|
||||
- If we fill elements in sorted order of ascending values, at every step we will know what ever is coming next will be greater than the given element.
|
||||
- We can use segment trees to do the range queries.
|
||||
|
||||
__Code__
|
||||
```C++
|
||||
void buildST(int s, int e, int index, vector<int> &tree){
|
||||
if(s == e){
|
||||
tree[index] = 1;
|
||||
return;
|
||||
}
|
||||
int mid = s + (e-s)/2;
|
||||
buildST(s, mid, index*2+1, tree);
|
||||
buildST(mid+1, e, index*2+2, tree);
|
||||
tree[index] = tree[index*2+1] + tree[index*2+2];
|
||||
}
|
||||
int getPos(vector<int>&tree, int s, int e, int index, int pos){
|
||||
if(s==e){
|
||||
tree[index]--;
|
||||
return s;
|
||||
}
|
||||
int mid = s+(e-s)/2;
|
||||
if(pos >= tree[index*2+1]){
|
||||
tree[index]--;
|
||||
return getPos(tree, mid+1, e,index*2+2, pos-tree[index*2+1]);
|
||||
}else{
|
||||
tree[index]--;
|
||||
return getPos(tree, s, mid, index*2+1, pos);
|
||||
}
|
||||
}
|
||||
vector<int> Solution::order(vector<int> &A, vector<int> &B) {
|
||||
int n = A.size();
|
||||
vector<int> tree(4*n);
|
||||
vector<pair<int,int>> person;
|
||||
for(int i = 0; i<n; i++){
|
||||
person.push_back({A[i],B[i]});
|
||||
}
|
||||
sort(person.begin(), person.end());
|
||||
buildST(0,n-1,0,tree);
|
||||
vector<int> res(n);
|
||||
for(auto p: person){
|
||||
int pos = getPos(tree,0,n-1,0,p.second);
|
||||
//cout << pos << endl;
|
||||
res[pos] = p.first;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
```
|
||||
__Time Complexity__
|
||||
- For each element we will spend $O(\log{n})$ time to find the proper position. Total time will be $O(n\log{n})$.
|
||||
- Space Complexity will be $O(n)$ as we will be storing the elements in a segment tree.
|
||||
|
||||
__Dry Run__
|
||||
```C++
|
||||
Heights: 1 2 3 4 5 6
|
||||
Infront: 3 2 1 2 0 0
|
||||
```
|
||||
|Segment Tree|Height|Infront|Answer|
|
||||
|:--|:--|:--|:--|
|
||||
|{0,5,6}{0,2,3}{3,5,3}{0,1,2}{2,2,1}{3,4,2}{5,5,1}{0,0,1}{1,1,1}{}{}{3,3,1}{4,4,1}{}{}|1|3|{, , ,1, , }|
|
||||
|{0,5,5}{0,2,3}{3,5,2}{0,1,2}{2,2,1}{3,4,1}{5,5,1}{0,0,1}{1,1,1}{}{}{3,3,0}{4,4,1}{}{}|2|2|{ , ,2,1, , }|
|
||||
|{0,5,4}{0,2,2}{3,5,2}{0,1,2}{2,2,0}{3,4,1}{5,5,1}{0,0,1}{1,1,1}{}{}{3,3,0}{4,4,1}{}{}|3|1|{,3,2,1, , }|
|
||||
|{0,5,3}{0,2,1}{3,5,2}{0,1,1}{2,2,0}{3,4,1}{5,5,1}{0,0,1}{1,1,0}{}{}{3,3,0}{4,4,1}{}{}|4|2|{,3,2,1, ,4}|
|
||||
|{0,5,2}{0,2,1}{3,5,1}{0,1,1}{2,2,0}{3,4,1}{5,5,0}{0,0,1}{1,1,0}{}{}{3,3,0}{4,4,1}{}{}|5|0|{5,3,2,1, ,4}|
|
||||
|{0,5,1}{0,2,0}{3,5,1}{0,1,0}{2,2,0}{3,4,1}{5,5,0}{0,0,0}{1,1,0}{}{}{3,3,0}{4,4,1}{}{}|6|0|{5,3,2,1,6,4}|
|
||||
|{0,5,0}{0,2,0}{3,5,0}{0,1,0}{2,2,0}{3,4,0}{5,5,0}{0,0,0}{1,1,0}{}{}{3,3,0}{4,4,0}{}{}||||
|
||||
|
||||
|
||||
|
||||
## Question 3 Merge Overlapping Intervals
|
||||
Given a collection of intervals, merge all overlapping intervals.
|
||||
|
||||
For example:
|
||||
|
||||
```C++
|
||||
Given [1,3],[2,6],[8,10],[15,18],
|
||||
|
||||
return [1,6],[8,10],[15,18].
|
||||
```
|
||||
|
||||
Make sure the returned intervals are sorted.
|
||||
|
||||
__Brute Force__
|
||||
- If we iterate over all the intervals and for each interval try to find another interval that is overlapping.
|
||||
- When we find an overlapping pair we can merge them and again start our iterations.
|
||||
- Time Complexity: For finding a pair we may take $O(n^2)$ time. Every time we merge a pair we reduce the list by one. There can be at maximum $O(n)$ pairs. So, the overall time complexity will be $O(n^3)$.
|
||||
|
||||
__Hint__
|
||||
- If we sort the intervals based on the starting time, all the overlapping itervals will be adjacent to each other.
|
||||
- We can then merge the overlapping intervals in linear time.
|
||||
|
||||
__Code__
|
||||
```C++
|
||||
/**
|
||||
* Definition for an interval.
|
||||
* struct Interval {
|
||||
* int start;
|
||||
* int end;
|
||||
* Interval() : start(0), end(0) {}
|
||||
* Interval(int s, int e) : start(s), end(e) {}
|
||||
* };
|
||||
*/
|
||||
bool compareIntervals(Interval a, Interval b){
|
||||
return a.start < b.start;
|
||||
}
|
||||
vector<Interval> Solution::merge(vector<Interval> &A) {
|
||||
// Do not write main() function.
|
||||
// Do not read input, instead use the arguments to the function.
|
||||
// Do not print the output, instead return values as specified
|
||||
// Still have a doubt. Checkout www.interviewbit.com/pages/sample_codes/ for more details
|
||||
sort(A.begin(), A.end(), compareIntervals);
|
||||
vector<Interval> res;
|
||||
int cur_start, cur_end;
|
||||
bool first = true;
|
||||
for(auto a:A){
|
||||
if(first){
|
||||
first = false;
|
||||
cur_start = a.start;
|
||||
cur_end = a.end;
|
||||
continue;
|
||||
}
|
||||
if(a.start <= cur_end){
|
||||
cur_end = max(a.end,cur_end);
|
||||
}else{
|
||||
Interval *i = new Interval(cur_start, cur_end);
|
||||
res.push_back(*i);
|
||||
cur_start = a.start;
|
||||
cur_end = a.end;
|
||||
}
|
||||
}
|
||||
Interval* i = new Interval(cur_start, cur_end);
|
||||
res.push_back(*i);
|
||||
return res;
|
||||
}
|
||||
```
|
||||
__Time Complexity__
|
||||
- $O(n)$
|
||||
|
||||
__Dry Run__
|
||||
```C++
|
||||
Array: [1,3],[2,6],[8,10],[15,18]
|
||||
```
|
||||
|cur_start|cur_end|cur_interval|Answer|
|
||||
|:--|:--|:--|:--|
|
||||
| | |[1,3]|{}|
|
||||
|1|3|[2,6]|{}|
|
||||
|1|6|[8,10]|{{1,6}}|
|
||||
|8|10|[15,18]|{{1,6},{8,10}}|
|
||||
|15|18| | {{1,6},{8,10},{15,18}}|
|
||||
|
||||
|
||||
## Question 4 Remove Invalid Parentheses
|
||||
Remove the minimum number of invalid parentheses in order to make the input string valid. Return all possible results.
|
||||
|
||||
Note: The input string may contain letters other than the parentheses ( and ).
|
||||
|
||||
Example 1:
|
||||
```C++
|
||||
Input: "()())()"
|
||||
Output: ["()()()", "(())()"]
|
||||
```
|
||||
Example 2:
|
||||
```C++
|
||||
Input: "(a)())()"
|
||||
Output: ["(a)()()", "(a())()"]
|
||||
```
|
||||
Example 3:
|
||||
```C++
|
||||
Input: ")("
|
||||
Output: [""]
|
||||
```
|
||||
__Brute Force__
|
||||
- If we are given a string of parentheses, we can check if it is valid or not. We can use a stack or a counter to do so. This will be an $O(n)$ algorithm.
|
||||
- We can recursively solve this question by keeping track of number of characters removed.
|
||||
- We will first remove no character and check the string. Then we will remove one character to iteratively using backtracking.
|
||||
- We will check when ever the number of characters to remove becomes zero.
|
||||
- When we find a valid string we can add it to the result set. As there can be multiple calls to same string.
|
||||
- Whenever the result array becomes non-empty we will stop increasing the number of characters to be removed and return.
|
||||
|
||||
__Hint__
|
||||
- Questions like this do not have good algorithms.
|
||||
- I am sharing it so that you understand the importance of Brute force.
|
||||
- However, you can apply some pruning to considerably improve the actual running time.
|
||||
- I have used several ideas to improve time complexity.
|
||||
- Count the number of bad parentheses. And you know your minimum.
|
||||
- I also counted number of bad open parentheses and bad close parentheses saperately.
|
||||
- I counted number of open and close parentheses so far.
|
||||
|
||||
__Code__
|
||||
```C++
|
||||
class Solution {
|
||||
public:
|
||||
bool isValid(string s){
|
||||
if(s=="")return true;
|
||||
int count = 0;
|
||||
for(auto c: s){
|
||||
if(c == '('){
|
||||
count++;
|
||||
}else if (c== ')'){
|
||||
if(count <= 0) return false;
|
||||
count--;
|
||||
}
|
||||
}
|
||||
if(count!=0) return false;
|
||||
return true;
|
||||
}
|
||||
void makevalid(string s, int open_to_remove, int close_to_remove, int open_count, int close_count, int from, unordered_set<string> &res){
|
||||
if(close_count > open_count) return;
|
||||
int toDelete = open_to_remove + close_to_remove;
|
||||
if(toDelete > s.size() - from){
|
||||
return;
|
||||
}
|
||||
if(toDelete == 0 && isValid(s)){
|
||||
res.insert(s);
|
||||
}else{
|
||||
for(int i = from; i<=s.size()-toDelete; i++){
|
||||
if(s[i] == ')' && close_to_remove>0){
|
||||
string newS = s.substr(0,i);
|
||||
newS += (i+1<s.size())? s.substr(i+1, s.size()-i-1):"";
|
||||
makevalid(newS,open_to_remove, close_to_remove-1, open_count, close_count, i, res);
|
||||
}
|
||||
if(s[i] == '(' && open_to_remove>0){
|
||||
string newS = s.substr(0,i);
|
||||
newS += (i+1<s.size())? s.substr(i+1, s.size()-i-1):"";
|
||||
makevalid(newS,open_to_remove-1, close_to_remove , open_count, close_count, i, res);
|
||||
}
|
||||
if(s[i] == ')') close_count++;
|
||||
else if(s[i] == '(') open_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
int findMin(string s, int &open_to_remove, int &close_to_remove){
|
||||
int ans = 0, count =0;
|
||||
for(auto c:s){
|
||||
if(c==')'){
|
||||
count--;
|
||||
}else if(c=='('){
|
||||
count++;
|
||||
}
|
||||
if(count < 0){
|
||||
ans++;
|
||||
count=0;
|
||||
}
|
||||
}
|
||||
open_to_remove = count;
|
||||
close_to_remove = ans;
|
||||
return ans+count;
|
||||
}
|
||||
vector<string> removeInvalidParentheses(string s) {
|
||||
unordered_set<string> res;
|
||||
int open_to_remove, close_to_remove;
|
||||
findMin(s, open_to_remove, close_to_remove);
|
||||
makevalid(s,open_to_remove, close_to_remove,0,0,0,res);
|
||||
vector<string> res2;
|
||||
for(auto s: res){
|
||||
res2.push_back(s);
|
||||
}
|
||||
return res2;
|
||||
}
|
||||
};
|
||||
```
|
||||
__Time Complexity__
|
||||
- $O(2^n)$ For every i, every element will either be removed or included.
|
||||
|
||||
__Dry Run__
|
||||
```C++
|
||||
s = "())"
|
||||
```
|
||||
|Main Call|Children Calls|res|
|
||||
|:--|:--|:--|
|
||||
|makevalid("())",1,0)|makevalid("))",0,0), makevalid("()",0,1), makevalid("()",0,2)| |
|
||||
|makevalid("))",0,0)| Invalid | |
|
||||
|makevalid("()",0,1)| Valid| {"()"}|
|
||||
|makevalid("()",0,2)| Valid| {"()"}|
|
Loading…
x
Reference in New Issue
Block a user