Skip to content

Latest commit

 

History

History
694 lines (606 loc) · 16.8 KB

基础05.md

File metadata and controls

694 lines (606 loc) · 16.8 KB

5. 二叉树

MybinaryTree.cpp

5.1 二叉树节点结构

struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode() : val(0), left(nullptr), right(nullptr) {}
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
    TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 };

5.2 使用递归和非递归两种方式实现二叉树的先序、中序、后序遍历

5.2.1 递归序

递归方法完成二叉树的遍历,每个节点都能回到三次。

void f(TreeNode* head){
    // 1
    if(head == nullptr){
        return;
    }
    // 1:第一次回到自己
    f(head->left);
    // 2
    // 2:第二次回到自己
    f(head->right);
    // 3
    // 3:第三次回到自己
}
  • 先序
void preOrderRecur(TreeNode* head){
    if(head == nullptr){
        return;
    }
    cout << head->value << " ";
    preOrderRecur(head->left);
    preOrderRecur(head->right);
}
  • 中序
void inOrderRecur(TreeNode* head){
    if(head == nullptr){
        return;
    }
    inOrderRecur(head->left);
    cout << head->value << " ";
    inOrderRecur(head->right);
}
  • 后序
void posOrderRecur(TreeNode* head){
    if(head == nullptr){
        return;
    }
    posOrderRecur(head->left);
    posOrderRecur(head->right);
    cout << head->value << " ";
}

5.2.2 非递归方法

  • 先序(深度优先遍历)

    • 先把头节点 head 放到栈中
    • (1) 从栈中弹出一个节点 cur
    • (2) 打印/处理 cur
    • (3) 先压右再压左 (没有就什么也不做)
    • (4) 循环
  • leetcode: 144. 二叉树的前序遍历

✅tested

vector<int> preorderTraversal(TreeNode* root) {
    vector<int> res;
    if(!root){
        return res;
    }
    stack<TreeNode*> s;
    s.push(root);
    while(!s.empty()){
        TreeNode* cur = s.top();
        s.pop();
        res.push_back(cur->val);
        if(cur->right){
            s.push(cur->right);
        }
        if(cur->left){
            s.push(cur->left);
        }
    }
    return res;
}
  • 后序 (将先序遍历加工)

    • 先序' (头右左)
    • 先把头节点 head 放到栈中
    • (1) 从栈中弹出一个节点 cur
    • (2) cur 放到收集栈中
    • (3) 先压左再压右 (没有就什么也不做)
    • (4) 循环
  • leetcode: 145. 二叉树的后序遍历

✅tested

vector<int> postorderTraversal(TreeNode* root) {
    vector<int> res;
    if(!root){
        return res;
    }
    stack<TreeNode*> s;
    s.push(root);
    stack<TreeNode*> sRes;
    while(!s.empty()){
        TreeNode* cur = s.top();
        s.pop();
        sRes.push(cur);
        if(cur->left){
            s.push(cur->left);
        }
        if(cur->right){
            s.push(cur->right);
        }
    }
    while(!sRes.empty()){
        res.push_back(sRes.top()->val);
        sRes.pop();
    }
    return res;
}

✅tested

vector<int> inorderTraversal(TreeNode* root) {
    vector<int> res;
    stack<TreeNode*> s;
    while(!s.empty() || root){
        while(root){
            s.push(root);
            root = root->left;      
        }
        root = s.top();
        s.pop();
        res.push_back(root->val);
        root = root->right;
    }
    return res;
}

5.3 如何完成二叉树的宽度优先遍历

5.3.1 宽度优先遍历

宽度优先遍历用 队列,弹出就打印,先放左再放右

vector<int> levelOrder(TreeNode* root) {
    vector<int> res;
    if(!root){
        return res;
    }
    queue<TreeNode*> q;
    q.push(root);
    while(!q.empty()){
        TreeNode* cur = q.front();
        res.push_back(cur->val);
        q.pop();
        if(cur->left){
            q.push(cur->left);
        }
        if(cur->right){
            q.push(cur->right);
        }
    }
    return res;
}

✅tested

vector<vector<int>> levelOrder(TreeNode* root) {
    vector<vector<int>> res;
    if(!root){
        return res;
    }
    queue<TreeNode*> q;
    q.push(root);
    TreeNode* curEnd = root;    //当前层的最后一个节点
    TreeNode* nextEnd = nullptr;//下一层的最后一个节点
    vector<int> tmp;
    while(!q.empty()){
        TreeNode* cur = q.front();
        q.pop();
        tmp.push_back(cur->val);
        if(cur->left){
            q.push(cur->left);
            nextEnd = cur->left;
        }
        if(cur->right){
            q.push(cur->right);
            nextEnd = cur->right;
        }
        if(cur == curEnd){
            res.push_back(tmp);
            tmp.clear();
            curEnd = nextEnd;
            nextEnd = nullptr;
        }
    }
    return res;
}

5.3.2 常见题目:求一颗二叉树的宽度

  • 使用哈希表的方法
int getMaxWidth(TreeNode* head){
    if(head == nullptr){
        return 0;
    }
    int maxWidth = 0;   // 哪一层的节点数目是最多的
    int curWidth = 0;
    int curLevel = 0;   // 当前在哪一层
    unordered_map<TreeNode*, int> levelMap;     //节点在第几层
    levelMap.insert(pair<TreeNode*, int>(head, 1));
    queue<TreeNode*> q;
    q.push(head);
    TreeNode* node = nullptr;
    TreeNode* left = nullptr;
    TreeNode* right = nullptr;
    while (!q.empty()){
        node = q.front();
        left = node->left;
        right = node->right;
        q.pop();
        int curLevelNodes = levelMap.at(node);  // 节点所在的层数
        if(curLevelNodes == curLevel){
            curWidth++;
        }
        else{
            maxWidth = max(maxWidth, curWidth);
            curLevel++;
            curLevelNodes = 1;
        }
        if(left != nullptr){
            levelMap.insert(pair<TreeNode*, int>(left, levelMap.at(node) + 1));
            q.push(left);
        }
        if(right != nullptr){
            levelMap.insert(pair<TreeNode*, int>(right, levelMap.at(node) + 1));
            q.push(right);
        }
    }
    return maxWidth;
}
  • 不使用哈希表的方法
int getMaxWidth2(TreeNode* root) {
    if(!root){
        return 0;
    }
    queue<TreeNode*> q;
    q.push(root);
    int maxWidth = -1;
    int curLevelNodes = 0;      // 当前层的节点数
    TreeNode* curEnd = root;    // 当前层的最后一个节点
    TreeNode* nextEnd = nullptr;// 下一层的最后一个节点
    while(!q.empty()){
        TreeNode* cur = q.front();
        q.pop();
        curLevelNodes++;
        if(cur->left){
            q.push(cur->left);
            nextEnd = cur->left;
        }
        if(cur->right){
            q.push(cur->right);
            nextEnd = cur->right;
        }
        if(cur == curEnd){
            maxWidth = max(maxWidth, curLevelNodes);
            curEnd = nextEnd;
            nextEnd = nullptr;
        }
    }
    return maxWidth;
}

5.4 二叉树的相关概念及其实现判断

5.4.1 如何判断一颗二叉树是否是搜索二叉树

搜索二叉树:

  • 若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
  • 若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
  • 任意节点的左、右子树也分别为搜索二叉树;

[提示] 中序遍历 是升序

✅tested

long long preValue = (long long)INT_MIN - 1;
bool isValidBST(TreeNode* root) {
    if(!root){
        return true;
    }
    bool isLeftBST = isValidBST(root->left);
    if(!isLeftBST){
        return false;
    }
    if(root->val <= preValue){
        return false;
    }
    preValue = root->val;
    return isValidBST(root->right);
}
  • 非递归写法

✅tested

bool isValidBST2(TreeNode* root) {
    if(!root){
        return true;
    }
    long long preValue = (long long)INT_MIN - 1;
    stack<TreeNode*> s;
    TreeNode* cur = root;
    while(!s.empty() || cur){
        while(cur){
            s.push(cur);
            cur = cur->left;
        }
        cur = s.top();
        s.pop();
        if(cur->val <= preValue){
            return false;
        }
        preValue = cur->val;
        cur = cur->right;
    }
    return true;
}
  • 递归套路解法

5.4.2 如何判断一颗二叉树是完全二叉树

[提示] 宽度优先遍历

(1) 任何一个节点,有右无左,返回false

(2) 在(1)不违规的条件下,如果遇到第一个左右子树不全的节点,后续节点必须都是叶节点

✅tested

bool isCompleteTree(TreeNode* root) {
    if(!root){
        return true;
    }
    bool isLeaf = false;    // 是否遇到过左右子树不全的节点
    queue<TreeNode*> q;
    q.push(root);
    while(!q.empty()){
        TreeNode* cur = q.front();
        q.pop();
        if((cur->right && !cur->left) || (isLeaf && (cur->left || cur->right))){
            return false;
        }
        if(cur->left){
            q.push(cur->left);
        }
        if(cur->right){
            q.push(cur->right);
        }
        if(!cur->left || !cur->right){
            isLeaf = true;
        }
    }
    return true;
}

5.4.3 如何判断一颗二叉树是否是满二叉树

[提示] 先求二叉树的最大深度L,再求二叉树的节点个数N

N = 2^L - 1

  • 二叉树的递归套路
struct Info{
    Info(int h, int n) : height(h),nodes(n){ }
    int height;
    int nodes;
};
Info f(TreeNode* head){
    if(!head){
        return Info(0, 0);
    }
    Info leftData = f(head->left);
    Info rightData = f(head->right);
    int height = max(leftData.height, rightData.height) + 1;
    int nodes = leftData.nodes + rightData.nodes + 1;
    return Info(height, nodes);
}
bool isFBT(TreeNode* head){
    if(head == nullptr){
        return true;
    }  
    Info data = f(head);
    return data.nodes == ((data.height << 1) - 1);
}

5.4.4 如何判断一颗二叉树是平衡二叉树

树型DP(动态规划)

二叉树的递归套路:

✅tested

struct ReturnType{
    bool m_isBal;
    int m_height;
    ReturnType(bool isBal, int height) : m_isBal(isBal), m_height(height) { }
};
ReturnType process(TreeNode* root){
    if(!root){
        return ReturnType(true, 0);
    }
    ReturnType leftInfo = process(root->left);
    ReturnType rightInfo = process(root->right);
    bool isBal = leftInfo.m_isBal && rightInfo.m_isBal && abs(leftInfo.m_height - rightInfo.m_height) < 2;
    int height = max(leftInfo.m_height, rightInfo.m_height) + 1;
    return ReturnType(isBal, height);
}
bool isBalanced(TreeNode* root) {
    if(!root){
        return true;
    }
    return process(root).m_isBal;
}

5.4.5 给定两个二叉树的节点node1和node2,找到他们的最低公共祖先节点

node1 和 node2 一定属于head为头的树

方法一:

  • 记录每个节点的头节点
  • 记录node1往上一直到头节点的节点有哪些
  • 比较node2往上的过程中是否遇到node1中记录的节点

✅tested

void process(TreeNode* root, unordered_map<TreeNode*, TreeNode*>& fatherMap){
    if(!root){
        return;
    }
    fatherMap.insert(pair<TreeNode*, TreeNode*>(root->left, root));
    fatherMap.insert(pair<TreeNode*, TreeNode*>(root->right, root));
    process(root->left, fatherMap);
    process(root->right, fatherMap);
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
    if(!root){
        return NULL;
    }
    unordered_map<TreeNode*, TreeNode*> fatherMap;
    fatherMap.insert(pair<TreeNode*, TreeNode*>(root, root));
    process(root, fatherMap);

    unordered_set<TreeNode*> s;

    TreeNode* cur = p;
    while(cur != fatherMap[cur]){
        s.insert(cur);
        cur = fatherMap[cur];
    }
    s.insert(root);
    
    cur = q;
    while(cur != fatherMap[cur]){
        if(s.find(cur) != s.end()){
            return cur;
        }
        cur = fatherMap[cur];
    }
    return root;
}  

方法二:

(1) o1是o2的LCA,或o2是o1的LCA

(2) o1与o2不互为LCA

✅tested

TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
    if(!root || root == p || root == q){
        return root;
    }
    TreeNode* left = lowestCommonAncestor(root->left, p, q);
    TreeNode* right = lowestCommonAncestor(root->right, p, q);
    if(left && right){
        return root;
    }
    return left != NULL? left : right;
}

5.4.6 在二叉树中找一个节点的后继节点

中序遍历中,一个节点的下一个节点

[题目] 现在有一种新的二叉树节点类型如下

struct TreeLinkNode {
    int val;
    struct TreeLinkNode *left;
    struct TreeLinkNode *right;
    struct TreeLinkNode *next;
    TreeLinkNode(int x) :val(x), left(NULL), right(NULL), next(NULL) { }
};

该结构比普通二叉树节点结构多了一个指向父节点的next指针。

假设有一棵Node类型的节点组成的二叉树,树中每个节点的next指针都正确地指向自己的父节点,头节点的next指向null。

只给一个在二叉树中的某个节点node,请实现返回node的后继节点的函数。

在二叉树的中序遍历的序列中,node的下一个节点叫作node的后继节点。

[提示]

(1) x 有右树时,右树上的最左节点

(2) x 无右树,往上走,判断当前节点是否是父节点的左孩子

(3) 整棵树最右侧的节点无后继节点

✅tested

TreeLinkNode* GetNext(TreeLinkNode* pNode) {
    if(pNode == nullptr){
        return nullptr;
    }
    TreeLinkNode* cur = pNode->right;
    if(cur != nullptr){
        while(cur->left){
            cur = cur->left;
        }
    }else{
        cur = pNode->next;
        while(cur->left != pNode && cur != nullptr){
            pNode = cur;
            cur = cur->next;
        }            
    }
    return cur;
}

5.4.7 二叉树的序列化和反序列化

就是内存里的一棵树如何变成字符串形式,又如何从字符串形式变成内存里的树

如何判断一颗二叉树是不是另一棵二叉树的子树?

✅tested

// Encodes a tree to a single string.
string serialize(TreeNode* root) {
    if(!root){
        return "#_";
    }
    string data = to_string(root->val) + "_";
    data += serialize(root->left);
    data += serialize(root->right);
    return data;
}
// Decodes your encoded data to tree.
TreeNode* deserialize(string data) {
    list<string> dataArray;
    string str;
    for(auto& ch : data){
        if(ch == '_'){
            dataArray.push_back(str);
            str.clear();
        }
        else{
            str.push_back(ch);
        }
    }
    if(!str.empty()){
        dataArray.push_back(str);
        str.clear();
    }
    return deserializeSub(dataArray);
}
TreeNode* deserializeSub(list<string>& dataArray){
    if(dataArray.front() == "#"){
        dataArray.erase(dataArray.begin());
        return NULL;
    }
    TreeNode* head = new TreeNode(stoi(dataArray.front()));
    dataArray.erase(dataArray.begin());
    head->left = deserializeSub(dataArray);
    head->right = deserializeSub(dataArray);
    return head;
}

5.4.8 折纸问题

请把一段纸条竖着放在桌子上,然后从纸条的下边向上方对折1次,压出折痕后展开。

此时折痕是凹下去的,即折痕突起的方向指向纸条的背面。

如果从纸条的下边向上方连续对折2次,压出折痕后展开,此时有三条折痕,从上到下依次是下折痕、下折痕和上折痕。

给定一个输入参数N,代表纸条都从下边向上方连续对折N次。

请从上到下打印所有折痕的方向。

例如:N=1时,打印:down。N=2时,打印:down down up

void printProcess(int i, int N, bool down){
    if(i > N){
        return;
    }
    printProcess(i+1, N, true);
    cout << (down ? "" : "") << " ";
    printProcess(i+1 , N, false);
}
void printAllfolds(int N){
    printProcess(1, N, true);
}