樹的面試題一般都是遞歸
1.節(jié)點(diǎn)的定義
2.重復(fù)性(自相似性)
遞歸Recursion
遞歸-循環(huán)
通過函數(shù)體來進(jìn)行的循環(huán)
遞歸和循環(huán)沒有明顯的邊界
1.從前有個(gè)山
2.山里有個(gè)廟
3.廟里有個(gè)和尚講故事
4.返回1
《盜夢(mèng)空間》
1.向下進(jìn)入到不同的夢(mèng)境中;向上又回到原來一層
2.通過聲音同步回到上一層
3.每一層的環(huán)境和周圍的人都是一根拷貝、主角等幾個(gè)人穿越不同層級(jí)的夢(mèng)境(發(fā)生和攜帶變化)
階乘
n! = 123...n;
def Factorial(n):
if n<=1
return 1;
return n*Factorial(n-1);
I 遞歸終結(jié)條件
II 處理當(dāng)前層邏輯
III 下探到下一層
IV 清理當(dāng)前層
Python代碼模板
def recursion(level,param1,param2,...)
# recursion terminator
if level > MAX_LEVEL;
process_result
return
#process logic in current level
process(level,data...)
# drill down
self.recursion(level+1,p1,...)
# reverse the current level status if needed
Java代碼模板
public void recur(int level,int param){
//ternimator
if(level>MAX_LEVEL){
//process result
return;
}
//process current logic
process(level,param);
//drill down
recur(level:level+1,newParam);
//restore current status
}
思維要點(diǎn)
1.不要人肉進(jìn)行遞歸(最大誤區(qū))----直接看函數(shù)本身開始寫
2.找到最近最簡(jiǎn)方法,將其拆解成可重復(fù)解決的問題(重復(fù)子問題)
3.數(shù)學(xué)歸納法思維
爬樓梯
假設(shè)你正在爬樓梯。需要 n 階你才能到達(dá)樓頂。
每次你可以爬 1 或 2 個(gè)臺(tái)階。你有多少種不同的方法可以爬到樓頂呢?
注意:給定 n 是一個(gè)正整數(shù)。
示例 1:
輸入: 2
輸出: 2
解釋: 有兩種方法可以爬到樓頂。
- 1 階 + 1 階
- 2 階
示例 2:
輸入: 3
輸出: 3
解釋: 有三種方法可以爬到樓頂。
- 1 階 + 1 階 + 1 階
- 1 階 + 2 階
- 2 階 + 1 階
方法一:傻遞歸
class Solution {
public int climbStairs(int n) {
//遞歸結(jié)束條件
if(n <= 2){
return n;
}
return climbStairs(n-1)+climbStairs(n-2);
}
}
復(fù)雜度分析
時(shí)間復(fù)雜度:O(2^n)
空間復(fù)雜度:O(n)
方法二:輔助數(shù)組
class Solution {
public int climbStairs(int n) {
if(n<=2)
return n;
int[] array = new int[n];
for(int i=0;i<n;i++){
if(i<=1){
array[i] = i + 1;
}else{
array[i] = array[i-1]+array[i-2];
}
}
return array[n - 1];
}
}
復(fù)雜度分析
時(shí)間復(fù)雜度:O(n)
空間復(fù)雜度:O(n)
方法三:輔助緩存
class Solution {
public int climbStairs(int n) {
if(n<=2)
return n;
int f1=1,f2=2,f3=3;
int i = 3;
while(i <= n){
f3 = f2 + f1;
f1 = f2;
f2 = f3;
i++;
}
return f3;
}
}
復(fù)雜度分析
時(shí)間復(fù)雜度:O(n)
空間復(fù)雜度:O(n)
方法四:動(dòng)態(tài)規(guī)劃
class Solution {
public int climbStairs(int n) {
int p = 0,q = 0,r = 1;
for(int i = 1;i <= n; i++){
p = q;
q = r;
r = p + q;
}
return r;
}
}
復(fù)雜度分析
時(shí)間復(fù)雜度:循環(huán)執(zhí)行n次,每次花費(fèi)常數(shù)的時(shí)間代價(jià),故漸進(jìn)時(shí)間復(fù)雜度為 O(n)。
空間復(fù)雜度:這里只用了常數(shù)個(gè)變量作為輔助空間,故漸進(jìn)空間復(fù)雜度為O(1)。
括號(hào)生成
數(shù)字 n 代表生成括號(hào)的對(duì)數(shù),請(qǐng)你設(shè)計(jì)一個(gè)函數(shù),用于能夠生成所有可能的并且 有效的 括號(hào)組合。
示例:
輸入:n = 3
輸出:[
"((()))",
"(()())",
"(())()",
"()(())",
"()()()"
]
public class Solution {
private List<String> result;
public List<String> generateParenthesis(int n){
result = new ArrayList<>();
_generate(0,0,n,"");
return result;
}
private void _generate(int left, int right, int max, String s) {
//terminator
if (left == max && right == max){
result.add(s);
return;
}
//process logic in current level
//drill down
if (left <= max)
_generate(left+1,right,max,s + "(");
if (right < left)
_generate(left,right+1,max,s + ")");
//reverse status
}
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.generateParenthesis(3));
}
}
//輸出
[((())), (()()), (())(), ()(()), ()()()]
驗(yàn)證二叉搜索樹
https://leetcode-cn.com/problems/validate-binary-search-tree/
需要注意的是不僅左子結(jié)點(diǎn)(右子結(jié)點(diǎn))需要小于(大于)該結(jié)點(diǎn),而且整個(gè)左子樹(右子樹)的所有的元素都應(yīng)該小于(大于)該結(jié)點(diǎn)。所以我們?cè)诒闅v的同時(shí)需要同結(jié)點(diǎn)的上界和下界比較。
方法一:遞歸 + 前序遍歷
上述的思路可以通過遞歸的方法實(shí)現(xiàn),將當(dāng)前結(jié)點(diǎn)的值與上下界進(jìn)行比較,然后在對(duì)左右子樹進(jìn)行遞歸。
過程如圖所示:
/**
* 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:
bool isValidBST(struct TreeNode* root) {
return helper(root,LONG_MIN,LONG_MAX);
}
bool helper(struct TreeNode* node,long long lower,long long upper){
if(node == NULL) return true;
long long value = node->val;
if(value <= lower || value >= upper)return false;
return helper(node->left,lower,value) && helper(node->right,value,upper);
}
};
方法二:遞歸 + 中序遍歷
我們也可以采用中序遍歷的方式,因?yàn)橹行虮闅v后的二叉搜索樹是有序的,所以只需判斷一次當(dāng)前結(jié)點(diǎn)的值即可。
/**
* 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:
TreeNode* pre = NULL;
bool isValidBST(TreeNode* root) {
if(root == NULL) return true;
if(!isValidBST(root->left)) return false;
if(pre != NULL && pre->val >= root->val) return false;
pre = root;
return isValidBST(root->right);
}
};
方法三:棧 + 中序遍歷
基于棧實(shí)現(xiàn)中序遍歷。
/**
* 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:
long long lower = LONG_MIN;
stack<TreeNode*> s;
bool isValidBST(TreeNode* root) {
while(root != NULL || !s.empty()){
while(root != NULL){
s.push(root);
root = root->left;
}
root = s.top();
s.pop();
if(root->val <= lower) return false;
lower = root->val;
root = root->right;
}
return true;
}
};
接下來講解一下如何使用棧實(shí)現(xiàn)前中后序遍歷,以棧實(shí)現(xiàn)中序遍歷為例,過程如下圖:
棧實(shí)現(xiàn)中序遍歷模板及關(guān)鍵點(diǎn):
/**
* 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:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> ret;
if(root == NULL) return ret;
stack<TreeNode *> s;
while(root!= NULL || !s.empty()){
while(root != NULL){
s.push(root);
root = root->left;
}
root = s.top();
s.pop();
ret.push_back(root->val);
root = root->right;
}
return ret;
}
};
棧實(shí)現(xiàn)前序遍歷代碼如下:
/**
* 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:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> ret;
if(root == NULL) return ret;
stack<TreeNode *> s;
s.push(root);
while(!s.empty()){
TreeNode * node = s.top();
ret.push_back(node->val);
s.pop();
if(node->left != NULL) s.push(node->left);
if(node->right != NULL) s.push(node->right);
}
return ret;
}
};
棧實(shí)現(xiàn)后序遍歷代碼如下:
/**
* 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:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> ret;
if(root == NULL) return ret;
stack<TreeNode *> s;
s.push(root);
while(!s.empty()){
TreeNode * node = s.top();
ret.push_back(node->val);
s.pop();
if(node->right != NULL) s.push(node->right);
if(node->left != NULL) s.push(node->left);
}
reverse(ret.begin(),ret.end());
return ret;
}
};
層序遍歷
層序遍歷非常好理解,就是將二叉樹的每一層分遍歷,直到最后所有的結(jié)點(diǎn)全部遍歷完成,這里我們需要的輔助數(shù)據(jù)結(jié)構(gòu)是隊(duì)列,因?yàn)樾枰玫疥?duì)列先進(jìn)先出的特性。
模板代碼如下:
/**
* 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:
vector<vector<int>> levelorderTraversal(TreeNode* root) {
vector<vector<int>> ret;
if(root == NULL) return ret;
queen<TreeNode *> q;
q.push(root);
while(!q.empty()){
int count = q.size();
ret.push_back(vector<int> ());
for(int i = 1;i <= count;i++){
TreeNode * node = q.front();
q.pop();
ret.back().push_back(node->val);
if(node->left)q.push(node->left);
if(node->right)q.push(node->right);
}
}
return ret;
}
};
二叉樹的最大深度
https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/
/**
* 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 maxDepth(TreeNode* root) {
if(root == NULL) return 0;
int left_Depth = maxDepth(root->left);
int right_Depth = maxDepth(root->right);
return (left_Depth > right_Depth? left_Depth:right_Depth)+1;
}
};