0. Overview
This time we will discuss some helpful data structures in algorithm. In many times, a good data structure could save us lots of energy, thus, besides the basic data structure vector, stack and queue, we will discuss below data structure:
- Hash
- Heap/ Priority Queue
- Union Find
- Trie Tree
- Stack (monotonous stack, reverse stack)
1. Data Type:
1.1 Hash
Operation: O(1) Insert / O(1) Find / O(1) Delete
Saturation of the hash table
Saturation = number of actual storage elements / total open space
Generally speaking, when it exceeds 1/10 (experience value), it needs rehash
1.1.1 Examples:
Using vector<Listnode* > to store the head of each index, hash table is to store the last node of the list of each index.
Notice:
C++/Java: if you directly calculate -4 % 3 you will get -1. You can use function: a % b = (a % b + b) % b to make it is a non negative integer.
Python: you can directly use -1 % 3, you will get 2 automatically.
vector<ListNode*> rehashing(vector<ListNode*> hashTable) {
// write your code here
if(hashTable.empty() )
return hashTable;
int n = hashTable.size() *2;
vector<ListNode*> result(n, nullptr);
unordered_map<int, ListNode*> hash;
for(auto node : hashTable) {
while(node) {
int index = (node->val % n + n) % n;
auto tmp = node ->next;
node ->next = nullptr;
if(hash.count(index)) {
hash[index] ->next = node;
hash[index] = hash[index] ->next;
} else {
result[index] = node;
hash[index] = node;
}
node = tmp;
}
}
return result;
}
List in C++ is doubly linked list, thus, it would be much easier than we thought.
#include<list>
class LRUCache {
private:
int limit;
list<int> cache;
unordered_map<int, int> hash;
public:
/*
* @param capacity: An integer
*/LRUCache(int capacity) {
// do intialization if necessary
limit = capacity;
}
/*
* @param key: An integer
* @return: An integer
*/
int get(int key) {
// write your code here
auto it = find(cache.begin(), cache.end(), key);
if(it == cache.end())
return -1;
cache.erase(it);
cache.push_back(key);
return hash[key];
}
/*
* @param key: An integer
* @param value: An integer
* @return: nothing
*/
void set(int key, int value) {
// write your code here
cache.remove(key);
if(cache.size() == limit)
cache.pop_front();
cache.push_back(key);
hash[key] = value;
}
};
vector<string> anagrams(vector<string> &strs) {
// write your code here
unordered_map<string, vector<string>> hash;
vector<string> result;
for(auto word : strs) {
string tmp = word;
sort(tmp.begin(), tmp.end());
if(hash.count(tmp)) {
hash[tmp].push_back(word);
} else {
hash[tmp] = vector<string> ();
hash[tmp].push_back(word);
}
}
for(auto now : hash) {
if(now.second.size() >= 2) {
result.insert(result.end(), now.second.begin(), now.second.end() );
}
}
return result;
}
124. Longest Consecutive Sequence
Using hash set for quick look-up of pre-number/ post-number.
To improve the efficiency, in hash set, while each element being visited, remove it from the hash set to save the space and time.
int longestConsecutive(vector<int> &nums) {
// write your code here
unordered_set<int> set(nums.begin(), nums.end() );
int result = -1;
for(auto num : nums) {
if(set.count(num)) {
set.erase(num);
int prev = num -1;
int post = num +1;
while(set.count(prev) )
set.erase(prev--);
while(set.count(post) )
set.erase(post++);
result = max(result, post - prev -1);
}
}
return result;
}
};
1.2 Heap/ Priority Queue
Operation: O(log N) Add / O(log N) Remove / O(1) Min or Max
1.2.1 Examples:
int nthUglyNumber(int n) {
int *uglys = new int[n];
uglys[0] = 1;
int next = 1;
int *p2 = uglys;
int *p3 = uglys;
int *p5 = uglys;
while (next < n){
int m = min(min(*p2 * 2, *p3 * 3), *p5 * 5);
uglys[next] = m;
while (*p2 * 2 <= uglys[next])
*p2++;
while (*p3 * 3 <= uglys[next])
*p3++;
while (*p5 * 5 <= uglys[next])
*p5++;
next++;
}
int uglyNum = uglys[n - 1];
delete[] uglys;
return uglyNum;
}
545. Top k Largest Numbers II
Simple implementation of priority_queue
class Solution {
private:
priority_queue<int> pq;
int limit;
public:
/*
* @param k: An integer
*/Solution(int k) {
// do intialization if necessary
limit = k;
}
/*
* @param num: Number to be added
* @return: nothing
*/
void add(int num) {
// write your code here
pq.push(num);
}
/*
* @return: Top k element
*/
vector<int> topk() {
// write your code here
vector<int> result;
int cnt = limit;
while(pq.size() > 0 && cnt) {
result.push_back(pq.top());
pq.pop();
cnt--;
}
for(auto num : result)
pq.push(num);
return result;
}
};
104. Merge K Sorted Lists
Practice writing the compare of priority_queue
class my_comparison {
public:
bool operator() (ListNode *n1, ListNode *n2) {
return n1 ->val > n2 ->val;
}
} ;
class Solution {
public:
/**
* @param lists: a list of ListNode
* @return: The head of one sorted list.
*/
ListNode *mergeKLists(vector<ListNode *> &lists) {
// write your code here
priority_queue<ListNode*, vector<ListNode*>, my_comparison> pq;
for(auto now : lists) {
while(now) {
ListNode *tmp = now ->next;
now ->next = nullptr;
pq.push(now);
now = tmp;
}
}
ListNode *dummy = new ListNode(0);
ListNode *ptr = dummy;
while( !pq.empty() ) {
ListNode *tmp = pq.top(); pq.pop();
ptr ->next = tmp;
ptr = ptr ->next;
}
return dummy ->next;
}
};
class cmp {
public:
bool operator() (Record a, Record b) {
return (a.score > b.score);
}
};
class Solution {
public:
/**
* @param results a list of <student_id, score>
* @return find the average of 5 highest scores for each person
* map<int, double> (student_id, average_score)
*/
map<int, double> highFive(vector<Record>& results) {
// Write your code here
// if (results.empty()) ??
map<int, double> ans;
unordered_map<int, int> id_to_row;
int n = results.size(), m = 0;
for (int i = 0; i < n; i++) {
if (id_to_row.count(results[i].id) == 0){ //没出现过
id_to_row[results[i].id]=m++;
}
}
vector<priority_queue<Record, vector<Record>, cmp>> pq(m);
for (int i = 0; i < n; i++) {
int row = id_to_row[results[i].id];
pq[row].push(results[i]);
if (pq[row].size() >5) {
pq[row].pop();
}
}
for (int i = 0; i < m; i++) {
int count = pq[i].size(), id = pq[i].top().id;
float sum = 0;
while (!pq[i].empty()) {
sum+=(pq[i].top().score);
pq[i].pop();
}
ans[id] = sum/count;
}
return ans;
}
};
class my_compare{
public:
bool operator() (pair<int, Point> &p1, pair<int, Point> &p2) {
int diff = p1.first - p2.first;
if(diff == 0)
diff = p1.second.x - p2.second.x;
if(diff == 0)
diff = p1.second.y - p2.second.y;
return diff > 0;
}
};
class Solution {
public:
/**
* @param points: a list of points
* @param origin: a point
* @param k: An integer
* @return: the k closest points
*/
vector<Point> kClosest(vector<Point> &points, Point &origin, int k) {
// write your code here
priority_queue<pair<int, Point>, vector<pair<int, Point>>, my_compare> pq;
for(auto point : points) {
int distance = pow((point.x - origin.x), 2) + pow((point.y - origin.y), 2);
pq.push(make_pair(distance, point));
}
vector<Point> result;
while(!pq.empty() && k) {\
auto tmp = pq.top(); pq.pop();
k--;
result.push_back(tmp.second);
}
return result;
}
};
vector<int> mergekSortedArrays(vector<vector<int>> &arrays) {
// write your code here
priority_queue<int,vector<int>, greater<int>> pq;
for(auto arr : arrays )
for(auto num : arr)
pq.push(num);
vector<int> result;
while(!pq.empty() ) {
result.push_back(pq.top());
pq.pop();
}
return result;
}
401. Kth Smallest Number in Sorted Matrix
Surprisingly, priority_queue has good performance.
int kthSmallest(vector<vector<int>> &matrix, int k) {
// write your code here
priority_queue<int, vector<int>, less<int>> pq;
for(auto arr : matrix)
for(auto num : arr) {
if(pq.size() < k) {
pq.push(num);
} else if(pq.top() > num) {
pq.pop();
pq.push(num);
}
}
return pq.top();
}
vector<int> topk(vector<int> &nums, int k) {
// write your code here
priority_queue<int> pq;
for(auto num : nums)
pq.push(num);
vector<int> result;
for(int i = 0; i < k; i++)
{
result.push_back(pq.top());
pq.pop();
}
return result;
}
81. Find Median from Data Stream
class Solution {
private:
vector<int> result;
int size = 0;
priority_queue<int> maxHeap;
priority_queue<int, vector<int>, greater<int>> minHeap;
public:
/**
* @param nums: A list of integers
* @return: the median of numbers
*/
vector<int> medianII(vector<int> &nums) {
// write your code here
for(auto num : nums) {
addNum(num);
result.push_back(getMedian());
}
return result;
}
void addNum(int num) {
maxHeap.push(num);
if(size % 2 == 0) {
if(minHeap.empty()) {
size++;
return;
} else if (maxHeap.top() > minHeap.top() ) {
// switch the roots of two priority_queue
int maxHeapRoot = maxHeap.top(); maxHeap.pop();
int minHeapRoot = minHeap.top(); minHeap.pop();
maxHeap.push(minHeapRoot);
minHeap.push(maxHeapRoot);
}
} else {
minHeap.push(maxHeap.top());
maxHeap.pop();
}
size++;
}
int getMedian() {
return maxHeap.top();
}
};
363. Trapping Rain Water
This problem is twp-pointer one, which is pre-heat of the next problem.
int trapRainWater(vector<int> &heights) {
// write your code here
int maxHeight = -1, maxIndex = -1;
int sum = 0;
for(int i = 0; i < heights.size(); i++){
if(heights[i] > maxHeight){
maxHeight = heights[i];
maxIndex = i;
}
}
// left part
maxHeight = -1;
for(int i = 0; i < maxIndex; i++){
if(maxHeight > heights[i]) {
sum += maxHeight - heights[i];
}
maxHeight = max(maxHeight, heights[i]);
}
// right part
maxHeight = -1;
for(int i = heights.size()-1; i > maxIndex; i--){
if(maxHeight > heights[i]) {
sum += maxHeight - heights[i];
}
maxHeight = max(maxHeight, heights[i]);
}
return sum;
}
int trapRainWater(vector<vector<int>> &heights) {
// write your code here
int m = heights.size();
int n = (m == 0)? 0 : heights[0].size();
priority_queue<pair<int, pair<int ,int>>,
vector<pair<int, pair<int ,int>>>,
greater<pair<int, pair<int ,int>>>> pq;
vector<vector<int>> visited(m, vector<int>(n, 0));
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
if( !(i == 0 || i == m-1 || j == 0 || j == n-1) ){
continue;
}
visited[i][j] = 1;
pq.push(make_pair(heights[i][j], make_pair(i, j)));
}
}
vector<int> dir = {0,1,0,-1,0};
int res = 0, H = INT_MIN;
while(!pq.empty()){
auto p = pq.top(); pq.pop();
int h = p.first, x = p.second.first, y = p.second.second;
H = max(H, h);
for(int i = 0; i < 4; i++) {
int nx = x + dir[i];
int ny = y + dir[i+1];
if(nx < 0 || nx > m-1 || ny < 0 || ny > n-1 || visited[nx][ny]){
continue;
}
visited[nx][ny] =1;
int diff = H - heights[nx][ny];
if(diff > 0)
res += diff;
pq.push(make_pair(heights[nx][ny], make_pair(nx, ny)));
}
}
return res;
}
360. Sliding Window Median
// notice here, the syntax one
// set.erase(const Iterator it), erase() need the const iterator, set.begin() is constant whereas set.rbegin() is not. Thus, we need *set.lower_bound(set.rbegin()) as const iterator of rbegin() element.
// windows problem:
// a. add an element
// b. remove an element
vector<int> medianSlidingWindow(vector<int> &nums, int k) {
// write your code here
vector<int> res;
multiset<int> min, max;
int n = nums.size();
if(n == 0)
return nums;
for(int i = 0; i < k; i++){
max.insert(nums[i]);
}
for(int i = 0; i < k/2; i++){
min.insert(*max.rbegin());
max.erase(max.lower_bound(*max.rbegin()));
}
for(int i = k; i < n; i++){
res.push_back(*max.rbegin());
if(max.find(nums[i-k]) != max.end()){
max.erase(max.find(nums[i-k]));
max.insert(nums[i]);
}
else{
min.erase(min.find(nums[i-k]));
min.insert(nums[i]);
}
if(max.size() > 0 && min.size() > 0 && *max.rbegin() > *min.begin()){
int tmp = *max.rbegin();
max.erase(max.lower_bound(*max.rbegin()));
max.insert(*min.begin());
min.erase(min.begin());
min.insert(tmp);
}
}
res.push_back(*max.rbegin());
return res;
}
1.3 Union Find
What’s Union Find?
A data structure for solving set query merge
Operations:
O(1)find/O(1)union
What’s Union Find for?
- Check whether in a set: find()
- Merge set: union()
- Query the number of one’s set
- Query the number of sets
Union Find Template
class UnionFind{
vector<int> father;
public:
int find(int x) {
if(father[x] == x )
return x;
return find(father[x]);
}
void union(int a, int b) {
int root_a = find(a);
int root_b = find(b);
if(root_a != root_b)
father[root_a] = root_b;
}
};
1.3.1 Examples:
589. Connecting Graph
Union Find is a perfect data structure to find the whether we elements were in the same component.
class ConnectingGraph {
vector<int> father;
public:
/*
* @param n: An integer
*/ConnectingGraph(int n) {
// do intialization if necessary
father.resize(n +1);
for(int i = 1; i <= n; i++ ) {
father[i] = i;
}
}
int find(int x) {
if(father[x] == x)
return x;
// it will update the father
return father[x] = find(father[x]);
}
/*
* @param a: An integer
* @param b: An integer
* @return: nothing
*/
void connect(int a, int b) {
// write your code here
int root_a = find(a);
int root_b = find(b);
if(root_a != root_b) {
father[root_a] = root_b;
}
}
/*
* @param a: An integer
* @param b: An integer
* @return: A boolean
*/
bool query(int a, int b) {
// write your code here
int root_a = find(a);
int root_b = find(b);
return root_a == root_b;
}
};
590. Connecting Graph II
In this one, we need to get the size of community, which means we need to record the size at the index of root node. After comparison, using vector instead of using unordered_map has a better performance as hash map.
class ConnectingGraph2 {
vector<int> hash;
vector<int> father;
public:
/*
* @param n: An integer
*/ConnectingGraph2(int n) {
// do intialization if necessary
father.resize(n +1);
hash.resize(n +1);
for(int i = 1; i <= n; i++) {
hash[i] = 1;
father[i] = i;
}
}
int find(int x) {
if(father[x] == x)
return x;
return father[x] = find(father[x]);
}
/*
* @param a: An integer
* @param b: An integer
* @return: nothing
*/
void connect(int a, int b) {
// write your code here
int root_a = find(a);
int root_b = find(b);
if(root_a != root_b) {
father[root_a] = root_b;
hash[root_b] += hash[root_a];
}
}
/*
* @param a: An integer
* @return: An integer
*/
int query(int a) {
// write your code here
int root_a = find(a);
return hash[root_a];
}
};
class ConnectingGraph3 {
int cnt = 0;
vector<int> father;
public:
/**
* @param a: An integer
* @param b: An integer
* @return: nothing
*/
ConnectingGraph3(int n) {
// initialize your data structure here.
father.resize(n +1);
cnt = n;
for(int i = 1; i < n +1; i++) {
father[i] = i;
}
}
int find(int x) {
if(father[x] == x)
return x;
return father[x] = find(father[x]);
}
void connect(int a, int b) {
// write your code here
int root_a = find(a);
int root_b = find(b);
if(root_a != root_b) {
cnt--;
father[root_a] = root_b;
}
}
/**
* @return: An integer
*/
int query() {
// write your code here
return cnt;
}
};
In this matrix problem, we transfer the 2D location into linear index. It not only saves us from complicate operations of 2D points, but also do the minimum modification on Find-Union data structure.
/**
* Definition for a point.
* struct Point {
* int x;
* int y;
* Point() : x(0), y(0) {}
* Point(int a, int b) : x(a), y(b) {}
* };
*/
class Solution {
public:
vector<int> father;
int find(int x) {
if(father[x] == x)
return x;
return father[x] = find(father[x]);
}
bool merge(int a, int b) {
if(father[a] == -1 || father[b] == -1)
return false;
int root_a = find(a);
int root_b = find(b);
if(root_a != root_b) {
father[root_a] = root_b;
return true;
}
return false;
}
/**
* @param n: An integer
* @param m: An integer
* @param operators: an array of point
* @return: an integer array
*/
vector<int> numIslands2(int n, int m, vector<Point> &operators) {
// write your code here
father.resize(n * m, -1);
int cnt = 0;
vector<int> result;
vector<int> dir {0,1,0,-1,0};
for(auto op : operators) {
int point = op.x * m + op.y;
if(father[point] == -1) {
cnt++;
father[point] = point;
}
for(int i = 0; i < 4; i++) {
int nx = op.x + dir[i];
int ny = op.y + dir[i +1];
if(0 <= nx && nx < n && 0 <= ny && ny < m) {
if(merge(nx * m + ny, point))
cnt--;
}
}
result.push_back(cnt);
}
return result;
}
};
178. Graph Valid Tree
To valid whether a graph is a tree, we could use union-find to store each node’s root node. In this case, every time we connect two node, all we need to do is to check whether their root nodes are the same, if so, then there exists a circle which cannot be a tree.
class Solution {
vector<int> root;
public:
/**
* @param n: An integer
* @param edges: a list of undirected edges
* @return: true if it's a valid tree, or false
*/
bool validTree(int n, vector<vector<int>> &edges) {
// write your code here
root.resize(n, -1);
for(auto edge : edges) {
int root1 = find(edge[0]);
int root2 = find(edge[1]);
if(root1 == root2) {
return false;
}
root[root1] = root2;
}
return n -1 == edges.size();
}
int find(int e) {
if(root[e] == -1)
return e;
else
return root[e] = find(root[e]);
}
};
BFS Version:
class Solution {
public:
/*
* @param board: board a 2D board containing 'X' and 'O'
* @return: nothing
*/
void surroundedRegions(vector<vector<char>> &board) {
// write your code here
int n = board.size();
if(n == 0)
return;
int m = board[0].size();
if(m == 0)
return;
vector<int> dirs {0, 1, 0, -1, 0};
for(int i = 0; i < n; i++) {
for(int j = 0; j < m; j++) {
if(i == 0 || i == n -1 || j == 0 || j == m -1) {
if(board[i][j] == 'O') {
queue<int> Q;
Q.push(i*m + j);
while(!Q.empty()) {
int tmp = Q.front(); Q.pop();
int x = tmp / m;
int y = tmp % m;
board[x][y] = '#';
for(int i = 0; i < 4; i++) {
int nx = x + dirs[i];
int ny = y + dirs[i +1];
if(0 <= nx && nx < n && 0 <= ny && ny < m && board[nx][ny] == 'O') {
Q.push(nx * m + ny);
}
}
}
}
}
}
}
for(int i = 0; i < n; i++) {
for(int j = 0; j < m; j++) {
if(board[i][j] == 'O')
board[i][j] = 'X';
if(board[i][j] == '#')
board[i][j] = 'O';
}
}
}
};
1.4 Trie Tree
What kinds of problem could related to Trie Tree?
- Implementation of Trie Tree
- Solve the problem according to the prefix of the tree
- Executing the DFS in a character matrix: traversal both DFS tree and Trie Tree
1.4.1 Examples:
442. Implement Trie (Prefix Tree)
class TrieNode {
public:
bool isString;
TrieNode* next[26];
TrieNode() {
isString = false;
for(int i = 0; i < 26; i++) {
next[i] = nullptr;
}
}
};
class Trie {
TrieNode* root;
public:
Trie() {
// do intialization if necessary
root = new TrieNode();
}
/*
* @param word: a word
* @return: nothing
*/
void insert(string &word) {
// write your code here
TrieNode* p = root;
for(char ch : word) {
if(p->next[ch - 'a'] == nullptr) {
p->next[ch - 'a'] = new TrieNode();
}
p = p->next[ch - 'a'];
}
p->isString = true;
}
/*
* @param word: A string
* @return: if the word is in the trie.
*/
bool search(string &word) {
// write your code here
TrieNode* p = root;
for(auto ch : word) {
if(p->next[ch - 'a'] == nullptr)
return false;
p = p->next[ch - 'a'];
}
return p->isString;
}
/*
* @param prefix: A string
* @return: if there is any word in the trie that starts with the given prefix.
*/
bool startsWith(string &prefix) {
// write your code here
TrieNode* p = root;
for(auto ch : prefix) {
if(p->next[ch - 'a'] == nullptr)
return false;
p = p->next[ch - 'a'];
}
return true;
}
};
473. Add and Search Word - Data structure design
class TrieNode {
public:
TrieNode* next[26];
bool isEnd;
TrieNode() {
isEnd = false;
for(int i = 0; i < 26; i++) {
next[i] = nullptr;
}
}
};
class WordDictionary {
TrieNode* root;
public:
WordDictionary() {
root = new TrieNode();
}
/*
* @param word: Adds a word into the data structure.
* @return: nothing
*/
void addWord(string &word) {
// write your code here
TrieNode* p = root;
for(auto ch : word) {
if(p->next[ch - 'a'] == nullptr) {
p->next[ch - 'a'] = new TrieNode();
}
p = p->next[ch - 'a'];
}
p->isEnd = true;
}
/*
* @param word: A word could contain the dot character '.' to represent any one letter.
* @return: if the word is in the data structure.
*/
bool search(string &word) {
// write your code here
return search(word, word.length(), 0, root);
}
bool search(string &word, int len, int pos, TrieNode* curr) {
if(!curr) return false;
if(pos == len) return curr->isEnd;
if(word[pos] == '.') {
for(int i = 0; i < 26; i++) {
if(curr->next[i] != nullptr) {
if(search(word, len, pos+1, curr->next[i]))
return true;
}
}
} else {
int index = word[pos] - 'a';
return search(word, len, pos +1, curr->next[index]);
}
return false;
}
};
132. Word Search II
This problem tagged as hard level, whereas the solution is the combination of Trie Tree and DFS. Thus, we need to write out the trie tree implement in minimum time and to figure out how to implement DFS.
In dfs part, firstly, what’s the end condition of dfs recursion? Secondly, with the help of ‘mask’ matrix to record whether current element is visited, we could avoid duplicate visiting of the same element.
struct Node {
Node* next[26];
string str;
Node(): str("") {
for(int i = 0; i < 26; i++) {
next[i] = nullptr;
}
}
};
class Solution {
Node* root;
vector<string> result;
public:
void insert(Node *p, string s) {
for(auto ch : s) {
if(!p->next[ch - 'a'])
p->next[ch - 'a'] = new Node();
p = p->next[ch - 'a'];
}
p->str = s;
}
/**
* @param board: A list of lists of character
* @param words: A list of string
* @return: A list of string
*/
vector<string> wordSearchII(vector<vector<char>> &board, vector<string> &words) {
// write your code here
root = new Node();
for(auto word : words)
insert(root, word);
if(board.empty())
return words;
vector<vector<bool>> mask(board.size(), vector<bool>(board[0].size(), true));
for(int i = 0; i < board.size(); i++) {
for(int j = 0; j < board[0].size(); j++) {
int index = board[i][j] - 'a';
if(root->next[index] )
// dfs search
search(board, mask, root->next[index], i, j);
}
}
return result;
}
void search(vector<vector<char>> &board, vector<vector<bool>> &mask, Node* p, int x, int y) {
// end condition for dfs
if(p->str != "") {
result.push_back(p->str);
p->str = "";
}
mask[x][y] = false;
vector<int> dirs{0,1,0,-1,0};
for(int i = 0; i < 4; i++) {
int nx = x + dirs[i];
int ny = y + dirs[i +1];
if(0 <= nx && nx < board.size() && 0 <= ny && ny < board[0].size() ) {
int index = board[nx][ny] - 'a';
//Don't forget to check the mask[nx][ny], or else it gonna back toward the former element
if(p->next[index] && mask[nx][ny])
search(board, mask, p->next[index], nx, ny);
}
}
mask[x][y] = true;
}
};
class Solution {
public:
struct TrieNode {
vector<int> indexs;
vector<TrieNode*> children;
TrieNode(): children(26, nullptr) {}
};
TrieNode* buildTrie(vector<string>& words) {
TrieNode *root = new TrieNode();
for (int i = 0; i < words.size(); ++i) {
TrieNode *t = root;
for (int j = 0; j < words[i].size(); ++j) {
if (!t->children[words[i][j] - 'a']) {
t->children[words[i][j] - 'a'] = new TrieNode();
}
t = t->children[words[i][j] - 'a'];
t->indexs.push_back(i);
}
}
return root;
}
vector<vector<string> > wordSquares(vector<string>& words) {
TrieNode *root = buildTrie(words);
vector<vector<string>> res;
if(words.size() == 0){
return res;
}
vector<string> out(words[0].size());
for (string word : words) {
out[0] = word;
helper(words, 1, root, out, res);
}
return res;
}
void helper(vector<string>& words, int level, TrieNode* root, vector<string>& out, vector<vector<string>>& res) {
if (level >= words[0].size()) {
res.push_back(out);
return;
}
string str = "";
for (int i = 0; i < level; ++i) {
str += out[i][level];
}
TrieNode *t = root;
for (int i = 0; i < str.size(); ++i) {
if (!t->children[str[i] - 'a']) return;
t = t->children[str[i] - 'a'];
}
for (int idx : t->indexs) {
out[level] = words[idx];
helper(words, level + 1, root, out, res);
}
}
};
1.5 Stack (monotonous stack, reverse stack)
Operation:
O(1) Push / O(1) Pop / O(1) Top
Monotonous stack
- Find the left or right of each element
- The first element smaller / larger than itself
- Use monotonic stack to maintain
Easily understanding the monotonous stack, it is a stack with ascending/descending order. When a new element is not obey this rule, we do some operations(pop out the stack one by one until it obeys) to satisfy the problem.
1.4.1 Examples:
12. Min Stack
Using two stack. One is the regular stack involves push, pop, and so on. The other stack is used to keep the record of min element. That means, each time a new element was pushed, we need to store the current minimum element.
class MinStack {
public:
stack<int> stk, minStk;
MinStack() {
// do intialization if necessary
}
/*
* @param number: An integer
* @return: nothing
*/
void push(int number) {
// write your code here
if(minStk.empty()) {
stk.push(number);
minStk.push(number);
} else {
int tmp = minStk.top();
if(tmp > number) {
stk.push(number);
minStk.push(number);
} else {
stk.push(number);
minStk.push(tmp);
}
}
}
/*
* @return: An integer
*/
int pop() {
// write your code here
int tmp = stk.top();
stk.pop();
minStk.pop();
return tmp;
}
/*
* @return: An integer
*/
int min() {
// write your code here
return minStk.top();
}
};
40. Implement Queue by Two Stacks
class MyQueue {
public:
stack<int> stk, stkQ;
MyQueue() {
// do intialization if necessary
}
/*
* @param element: An integer
* @return: nothing
*/
void push(int element) {
// write your code here
stk.push(element);
}
/*
* @return: An integer
*/
int pop() {
// write your code here
if( stkQ.empty() ) {
while(! stk.empty() ) {
stkQ.push(stk.top());
stk.pop();
}
}
int tmp = stkQ.top();
stkQ.pop();
return tmp;
}
/*
* @return: An integer
*/
int top() {
// write your code here
if( stkQ.empty() ) {
while(! stk.empty() ) {
stkQ.push(stk.top());
stk.pop();
}
}
return stkQ.top();
}
};
575. Decode String
Two stacks to record the information of the string. One stack is to push the int, which would the repetition number; The other is to record the content/string needed to be repeated.
class Solution {
public:
/**
* @param s: an expression includes numbers, letters and brackets
* @return: a string
*/
string expressionExpand(string &s) {
// write your code here
stack<string> stk_str;
stack<int> stk_cnt;
int count = 0;
string str = "";
string tmp;
for(auto ch : s) {
// 1. ch is a number
if('0' <= ch && ch <= '9') {
count = count * 10 + (ch - '0');
}
// 2. ch is '['
else if (ch == '[') {
stk_cnt.push(count);
cout << count << " ";
count = 0;
stk_str.push(str);
str = "";
}
// 3. ch is ']'
else if (ch == ']') {
int cnt = stk_cnt.top(); stk_cnt.pop();
string tmp = stk_str.top(); stk_str.pop();
for(int i = 0; i < cnt; i++)
tmp += str;
cout << tmp << " ";
str = tmp;
count = 0;
}
// 4. ch is character fropm 'a' to 'z'
else {
str += ch;
}
}
return str;
}
};
122. Largest Rectangle in Histogram
int largestRectangleArea(vector<int> &height) {
// write your code here
stack<int> stk;
if(height.empty()) return 0;
height.push_back(0);
int result = 0;
for(int i = 0; i < height.size(); i++) {
// if the the height[i] is bigger than the top element in stack
if(stk.empty() || height[i] > height[stk.top()]) {
stk.push(i);
}
// if the height[i] is smaller than top element in stack
// we pop out the stack to compare the result.
else {
while(!stk.empty() && height[i] < height[stk.top()]) {
int idx = stk.top(); stk.pop();
int width = stk.empty()? i : (i - stk.top() -1);
result = max(result, height[idx] * width);
}
stk.push(i);
}
}
return result;
}
510. Maximal Rectangle
Translate the matrix into histogram, then iterate each row as a collection of height.
class Solution {
public:
/**
* @param matrix: a boolean 2D matrix
* @return: an integer
*/
int maximalRectangle(vector<vector<bool>> &matrix) {
// write your code here
if(matrix.empty() || matrix[0].empty())
return 0;
int m = matrix.size();
int n = matrix[0].size();
vector<vector<int>> heights(m, vector<int>(n, 0));
for(int i = 0; i < m; i++) {
for(int j = 0; j < n; j++) {
if(matrix[i][j]) {
heights[i][j] = (i == 0)? 1 : heights[i -1][j] +1;
}
}
}
int result = 0;
for(int i = 0; i < m; i++) {
result = max(result, getMaxArea(heights[i]));
}
return result;
}
int getMaxArea(vector<int> &heights) {
stack<int> stk;
int res = 0;
// used to do the final check out
heights.push_back(0);
for(int i = 0; i < heights.size(); i++) {
if(stk.empty() || heights[i] > heights[stk.top()] ) {
stk.push(i);
}
else {
while(!stk.empty() && heights[i] < heights[stk.top()]) {
int idx = stk.top(); stk.pop();
int width = stk.empty()? i : (i - stk.top() -1);
res = max(res, heights[idx] * width);
}
stk.push(i);
}
}
return res;
}
};
class Solution {
public:
vector<TreeNode*> stk;
/**
* @param A: Given an integer array with no duplicates.
* @return: The root of max tree.
*/
TreeNode * maxTree(vector<int> &A) {
// write your code here
for(auto num : A) {
TreeNode* tmp = new TreeNode(num);
if(!stk.empty() && num > stk[stk.size() -1] ->val) {
while(!stk.empty() && num > stk[stk.size() -1] ->val){
tmp->left = stk[stk.size() -1];
stk.pop_back();
}
}
if(!stk.empty() && num < stk[stk.size() -1] ->val) {
stk[stk.size() -1] ->right = tmp;
}
stk.push_back(tmp);
}
return stk[0];
}
};