0. Basic Concepts
0.1 Binary Tree
- At the depth of i, the number of nodes is 2^(i-1) at most.
- For a tree whose depth is k, the number of nodes is 2^(k-1) at most
0.2 Full Binary Tree
The binary tree that each node has two or none children.
0.3 Binary Search Tree
For this type of tree, the left child node is smaller than its parent node, while the right child node is greater than its parent node.
0.4 Divide and Conquer:
Due to the feature of binary tree, tree problem can be treated as divide-and-conquer problem.
Three Elements:
- Divide: decompose the original problem into same type sub-problem
- Conquer: solve sub-problem by recursing. If the it’s the base case, return the answer directly.
- Combine/ Merge: Merge the answer of all the problems.
1. Problem Type:
Tips:
- For Binary Tree problem, pay attention to the relationship between final result and the results on left/right child.
1.0 Traverse in Binary Tree
Three types of traverse in binary tree: pre-order, in-order, post-order.
1.0.1 Examples:
66. Binary Tree Preorder Traversal
Note here, use the insert() method of vector.
vector<int> preorderTraversal(TreeNode * root) {
// write your code here
vector<int> result;
if(!root)
return result;
// Divide
vector<int> left = preorderTraversal(root ->left);
vector<int> right = preorderTraversal(root ->right);
// Conquer (Merge)
result.push_back(root->val);
result.insert(result.end(), left.begin(), left.end());
result.insert(result.end(), right.begin(), right.end());
return result;
}
67. Binary Tree Inorder Traversal
68. Binary Tree Postorder Traversal
1.1 DFS:
- The implement of DFS in tree is usually recursion. In interview, most tree problem can be handled as recursion.
- We usually use Stack to turn the recursion into non-recursion.
1.1.1 Examples:
int maxDepth(TreeNode * root) {
// write your code here
if(!root)
return 0;
return max(maxDepth(root->left), maxDepth(root->right)) + 1;
}
88. Lowest Common Ancestor of a Binary Tree
TreeNode * lowestCommonAncestor(TreeNode * root, TreeNode * A, TreeNode * B) {
// write your code here
if(!root || A == root || B == root)
return root;
TreeNode * left = lowestCommonAncestor(root ->left, A, B);
TreeNode * right = lowestCommonAncestor(root ->right, A, B);
if(left && right) return root;
if(left) return left;
if(right) return right;
return nullptr;
}
596. Minimum Subtree
Write a helper function to decide the current sum of root, and then update the global variable: subtree, subSum.
class Solution {
private:
TreeNode * subtree = nullptr;
int subSum = INT_MAX;
public:
/**
* @param root: the root of binary tree
* @return: the root of the minimum subtree
*/
TreeNode * findSubtree(TreeNode * root) {
// write your code here
helper(root);
return subtree;
}
int helper(TreeNode * root)
{
if(! root)
return 0;
int sum = helper(root ->left) + helper(root ->right) + root->val;
if(sum < subSum)
{
subSum = sum;
subtree = root;
}
return sum;
}
};
1.2 Level-Traversal/ BFS:
Though BFS was mentioned mostly in graph situation, the tree can be regraded as special tree.
- Visit the nodes level by level.
- It belongs to the BFS.
However, Level traversal problem usually related to the serialization problem. For example, transform a tree into json format.
1.2.1 Examples:
69. Binary Tree Level Order Traversal
In level traversal problem, there points to notice:
- using queue, instead of stack
- Use second loop to iterate each level
- size = queue.size() to confirm the next level size.
vector<vector<int>> levelOrder(TreeNode * root) {
// write your code here
vector<vector<int>> result;
queue<TreeNode *> stk;
if(root)
stk.push(root);
while(!stk.empty())
{
int size = stk.size();
vector<int> level_result;
for(int i = 0; i < size; i++)
{
TreeNode * tmp = stk.front();
stk.pop();
level_result.push_back(tmp->val);
if(tmp->left) stk.push(tmp->left);
if(tmp->right) stk.push(tmp->right);
}
result.push_back(level_result);
}
return result;
}
70. Binary Tree Level Order Traversal II
Similar idea with previous problem, except the order. To attain the bottom-up order, we can 1. use push_front() instead of push_back; 2. attain the top-down order, and then reverse the result.
71. Binary Tree Zigzag Level Order Traversal
Level traversal problem, except it need to zigzag. We will first write the level traversal code, then and a bool to check whether current level need to be reversed.
242. Convert Binary Tree to Linked Lists by Depth
It just transform the vector into tree structure.
7. Serialize and Deserialize Binary Tree
tip:
a.note the serialize() function, in the first for-loop, it’s i < queue.size() instead of a const.
class Solution {
public:
/**
* This method will be invoked first, you should design your own algorithm
* to serialize a binary tree which denote by a root node to a string which
* can be easily deserialized by your own "deserialize" method later.
*/
string serialize(TreeNode * root) {
// write your code here
if(!root)
return "{}";
vector<TreeNode* > queue;
queue.push_back(root);
// 1. store the tree into vector
for(int i = 0; i < queue.size(); i++){
if(queue[i] == nullptr)
continue;
queue.push_back(queue[i] ->left);
queue.push_back(queue[i] ->right);
}
// serialize the vector into string
string result = "";
result += "{";
result += to_string(queue[0] ->val);
for(int i = 1; i < queue.size(); i++){
if(queue[i] == nullptr){
result += ",#";
} else {
result += ",";
result += to_string(queue[i] ->val);
}
}
result += "}";
return result;
}
/**
* This method will be invoked second, the argument data is what exactly
* you serialized at method "serialize", that means the data is not given by
* system, it's given by your own serialize method. So the format of data is
* designed by yourself, and deserialize it here as you serialize it in
* "serialize" method.
*/
TreeNode * deserialize(string &data) {
// write your code here
if(data == "{}")
return nullptr;
vector<string> tokens = split(data.substr(1, data.length() -2), ',');
TreeNode* root = new TreeNode(stoi(tokens[0]));
TreeNode* ptr = root;
queue<TreeNode* > queue;
queue.push(root);
bool isLeft = true;
for(int i = 1; i < tokens.size(); i++){
string child = tokens[i];
if(child != "#"){
TreeNode* tmp = new TreeNode(stoi(tokens[i]));
if(isLeft)
queue.front() ->left = tmp;
else
queue.front() ->right = tmp;
queue.push(tmp);
}
if(!isLeft)
queue.pop();
isLeft = !isLeft;
}
return root;
}
vector<string> split(const string& s, char delimiter){
vector<string> tokens;
string token;
istringstream tokenStream(s);
while(getline(tokenStream, token, delimiter) ){
tokens.push_back(token);
}
return tokens;
}
};
1.3 Binary Search Tree (BST):
Definition:
- All the left nodes is smaller than root
- All the right nodes is larger than root
Characteristic:
- The in-order traversal is ascending order
1.3.1 Examples:
95. Validate Binary Search Tree
This problem involves the ResultType class. To validate a BST, we need to know the three things of its left/right subtree, which would be isBST, minNode, maxNode. Thus, we encapsulate these three into Result Type as the result of helper function.
class ResultType {
public:
bool isBST;
TreeNode* minNode;
TreeNode* maxNode;
ResultType(): isBST(true), minNode(nullptr), maxNode(nullptr){}
};
class Solution {
public:
/**
* @param root: The root of binary tree.
* @return: True if the binary tree is BST, or false
*/
bool isValidBST(TreeNode * root) {
// write your code here
ResultType result = helper(root);
return result.isBST;
}
ResultType helper(TreeNode* root)
{
ResultType result;
if(!root)
return result;
ResultType left = helper(root ->left);
ResultType right = helper(root ->right);
if(! left.isBST || ! right.isBST){
result.isBST = false;
return result;
}
if(left.maxNode && left.maxNode->val >= root ->val)
{
result.isBST = false;
return result;
}
if(right.minNode && right.minNode ->val <= root -> val){
result.isBST = false;
return result;
}
result.isBST = true;
result.minNode = !left.minNode? root : left.minNode;
result.maxNode = !right.maxNode? root : right.maxNode;
return result;
}
};
86. Binary Search Tree Iterator
class BSTIterator {
public:
stack<TreeNode *> myStack;
TreeNode *current;
BSTIterator(TreeNode *root) {
while (!myStack.empty()) {
myStack.pop();
}
current = root;
}
/** @return whether we have a next smallest number */
bool hasNext() {
return (current != NULL || !myStack.empty());
}
/** @return the next smallest number */
TreeNode* next() {
while (current != NULL) {
myStack.push(current);
current = current->left;
}
current = myStack.top(); myStack.pop();
TreeNode *nxt = current;
current = current->right;
return nxt;
}
};
TreeNode * inorderSuccessor(TreeNode * root, TreeNode * p) {
// write your code here
if(!root)
return root;
if(root->val <= p->val)
return inorderSuccessor(root->right, p);
TreeNode* left = inorderSuccessor(root->left, p);
return left? left: root;
}
11. Search Range in Binary Search Tree
Write a dfs helper function to traverse the tree, push the val into result if it satisfies the requirements.
class Solution {
public:
/**
* @param root: param root: The root of the binary search tree
* @param k1: An integer
* @param k2: An integer
* @return: return: Return all keys that k1<=key<=k2 in ascending order
*/
vector<int> result;
vector<int> searchRange(TreeNode * root, int k1, int k2) {
// write your code here
if(!root)
return result;
dfs(root, k1, k2);
return result;
}
void dfs(TreeNode * root, int k1, int k2) {
if(!root)
return;
dfs(root ->left, k1, k2);
if(root ->val >= k1 && root -> val <= k2 )
result.push_back(root ->val);
dfs(root ->right, k1, k2);
}
};
85. Insert Node in a Binary Search Tree
TreeNode * insertNode(TreeNode * root, TreeNode * node) {
// write your code here
if(!root)
return node;
TreeNode* current = root, *pre_node = nullptr;
while(current)
{
pre_node = current;
if(current ->val > node->val)
{
current = current ->left;
}
else {
current = current ->right;
}
}
if(pre_node ->val > node->val)
pre_node ->left = node;
else
pre_node ->right = node;
return root;
}
87. Remove Node in Binary Search Tree
The advanced operation on BTS. Pay attention to the replace of the new node. The main idea would be:
- Locate the target node. If not exists, return the original one.
- Strategically modify the BST.
How to delete the target node and modify the BST? Let the target node be “target”
a. check whether target->right is null
b. if not, check target->right->left is nul
c. if not, find the next in-order node of target
TreeNode* removeNode(TreeNode* root, int value) {
// write your code here
if (root == NULL)
return NULL;
TreeNode * head = new TreeNode();
head->left = root;
TreeNode * tmp = root, *father = head;
while (tmp != NULL) {
if (tmp->val == value)
break;
father = tmp;
if (tmp->val > value)
tmp = tmp->left;
else
tmp = tmp->right;
}
if (tmp == NULL)
return head->left;
if (tmp->right == NULL) {
if (father->left == tmp)
father->left = tmp->left;
else
father->right = tmp->left;
} else
if (tmp->right->left == NULL) {
if (father->left == tmp)
father->left = tmp->right;
else
father->right = tmp->right;
tmp->right->left = tmp->left;
} else {
father = tmp->right;
TreeNode * cur = tmp->right->left;
while (cur->left != NULL) {
father = cur;
cur = cur->left;
}
tmp->val = cur->val;
father->left = cur->right;
}
return head->left;
}
1.4 Tree Path Problem
This kind of problem will need you find out all/a paths in a tree, which satisfy certain requirements.
In this kind problem, we will use path(vector< int>) and result(vector<vector< int>>) as a record to store the path we have.
1.4.1 Examples:
vector<string> binaryTreePaths(TreeNode * root) {
// write your code here
vector<string> paths;
if(!root)
return paths;
if(! root ->left && ! root ->right)
{
paths.push_back(to_string(root ->val));
return paths;
}
vector<string> left = binaryTreePaths(root ->left);
vector<string> right = binaryTreePaths(root ->right);
for(auto path : left)
{
paths.push_back(to_string(root ->val) + "->" + path);
}
for(auto path : right)
{
paths.push_back(to_string(root ->val) + "->" + path);
}
return paths;
}
1.5 Switch Tree between other Data Structure
This kind of problem will need you convert the tree into other structure, e.g. linked list, array. Or vice versa.
If it is the former type–convert tree into other structure, it usually applying DFS or BFS.
If it is the latter type–convert other structure into tree, it usually solves problem by D&C. Recursively divide the data into two subtrees.
1.5.1 Examples:
1534. Convert Binary Search Tree to Sorted Doubly Linked List
Tricky solution.
class Solution {
public:
TreeNode* treeToDoublyList(TreeNode* root) {
// Write your code here.
if(root == nullptr)
return nullptr;
TreeNode* first = nullptr;
TreeNode* last = nullptr;
treeToDLLUtil(root, &first, &last);
last->right = first;
first->left = last;
return first;
}
private:
void treeToDLLUtil(TreeNode* root, TreeNode** first, TreeNode** last){
if(root == nullptr)
return;
treeToDLLUtil(root->left, first, last);
if(*first == nullptr){
*first = root;
*last = root;
} else {
(*last)->right = root;
root->left = *last;
*last = root;
}
treeToDLLUtil(root->right, first, last);
}
};
453. Flatten Binary Tree to Linked List
class Solution {
public:
/**
* @param root: a TreeNode, the root of the binary tree
* @return: nothing
*/
void flatten(TreeNode * root) {
// write your code here
helper(root);
}
TreeNode* helper(TreeNode* root)
{
if(!root)
return root;
TreeNode* leftLast = helper(root->left);
TreeNode* rightLast = helper(root->right);
if(leftLast)
{
leftLast->right = root->right;
root->right = root->left;
root->left = nullptr;
}
if(rightLast)
{
return rightLast;
}
if(leftLast)
return leftLast;
return root;
}
};
1.6 Search Specific Node
This kind of problem usually input a node, and require to find another node, which can be its pre-oder node, post-order node, left/ right brother node.
If it is a doubly linked list, we could just bottom up by parent pointer. If it is singly linked list, we can only search down by DFS.
1.6.1 Examples:
1.7 Trie Tree
A trie or prefix tree is a 26-ary tree used to retrieve a string, or string prefix, from a collection. Each node of the dictionary tree has an array of pointers to represent all its subtrees, which is essentially a hash table, because the location of the subtree (index) itself represents the letter corresponding to the node.
1.7.1 Examples:
1.8 Binary Tree with Return Type
In this type of problem, it would be more convenient for us to write a customized the return type; e.g.
class ResultType { int var1, var2; }
1.8.1 Example
This problem is just let you have sense of return type. Sometimes, you don’t have to spend time creating a class; Instead, all you need is to return specific result as your identical result. For example, in this problem, -1 represents it’s not a balanced tree. If it returns a positive number, then it’s the height of the tree.
Thus, as we only need to know whether it’s a balanced tree, we could use -1 as flag to note.
Finally, don’t forget to +1 in the last line of helper function, the result represents the current depth of level.
class Solution {
public:
/**
* @param root: The root of binary tree.
* @return: True if this Binary tree is Balanced, or false.
*/
bool isBalanced(TreeNode * root) {
// write your code here
return helper(root) != -1;
}
int helper(TreeNode * root)
{
if(!root)
return 0;
int left = helper(root ->left);
int right = helper(root ->right);
if(left == -1 || right == -1 || abs(left - right) > 1)
return -1;
return max(left, right) + 1;
}
};
597. Subtree with Maximum Average
This is good example of ResultType problem. We need to put the necessary information into the Result Type.
Then, in divide and conquer part, merge the result.
class ResultType {
public:
int sum;
int size;
ResultType(): sum(0), size(0){}
ResultType(int _sum, int _size): sum(_sum), size(_size){}
} ;
class Solution {
private:
TreeNode* node = nullptr;
ResultType data;
public:
/**
* @param root: the root of binary tree
* @return: the root of the maximum average of subtree
*/
TreeNode * findSubtree2(TreeNode * root) {
// write your code here
helper(root);
return node;
}
ResultType helper(TreeNode* root){
if(!root)
return ResultType();
// divide
ResultType left = helper(root ->left);
ResultType right = helper(root ->right);
// conquer
ResultType rootResult = ResultType(
root ->val + left.sum + right.sum,
1+ left.size + right.size);
// replace / expression with * expression
// if there is no result or need to update result
if(!node || data.sum *rootResult.size < rootResult.sum * data.size ){
data = rootResult;
node = root;
}
return rootResult;
}
};