数据结构与算法汇总整理篇——链表与二叉树(常用特性的概念及细节处理)

链表与典型的树结构是通过使用指针变量进行链接构建的。

链表

链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现。链表它是由一个一个节点连接起来的。每一个节点里面至少有两个值,data存放的是数据,next存放的是下一个节点的地址。

cpp 复制代码
typedef int DataType;
 
typedef struct ListNode
{
	DataType val;
	struct ListNode* next;
}ListNode;

链表的种类: 带头不带头,循环不循环,单向或双向 (可以不同类型相互组合)

无头单向非循环链表:**结构简单**,一般不会单独用来存数据。实际中更多是作为**其他数据结构的子结构**,如哈希桶、图的邻接表等等。这种结构在**笔试面试**中出现很多。

带头双向循环链表:**结构最复杂**,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了。

单向链表

定义(关键)

cpp 复制代码
struct ListNode {
    int val;
    ListNode *next;
    ListNode() : val(0), next(nullptr) {}
    ListNode(int x) : val(x), next(nullptr) {}
    ListNode(int x, ListNode *next) : val(x), next(next) {}
};

移除链表元素

cpp 复制代码
class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        ListNode* sentrynode = new ListNode(-1,head);
        ListNode* cur = sentrynode;


        while(cur->next){
            ListNode* nxt = cur->next;
            if(nxt->val == val){                //必须同步进行
                cur->next = nxt->next;
                delete(nxt);
            }else{
                cur = nxt;
            }
        }

        head = sentrynode->next;
        delete(sentrynode);

        return head;
    }
};

设计链表

cpp 复制代码
class MyLinkedList {
    private:
        struct ListNode{
            int val;
            ListNode* next;
            ListNode(int v, ListNode*nxt): val(v), next(nxt) {}
            ListNode(int v): val(v), next(nullptr) {} 
        };

        ListNode* sentrynode;
        int list_size;
public:
    MyLinkedList() {
        this->sentrynode = new ListNode(-1, nullptr);
        this->list_size = 0;
    }
    
    int get(int index) {
        if(index > (list_size -1) || index <0)
            return -1;

        ListNode* cur = sentrynode;
 
        while(index--){
            cur = cur->next;
        }
       

        return cur->next->val;
    }
    
    void addAtHead(int val) {

        ListNode* node = new ListNode(val);

        node->next = sentrynode->next;
        sentrynode->next = node;
        list_size++;
    }
    
    void addAtTail(int val) {
        int count = list_size;
        ListNode* cur = sentrynode;

        while(count--){
            cur = cur->next;
        }

        ListNode* node = new ListNode(val);
        cur->next = node;
        list_size++;
    }
    
    void addAtIndex(int index, int val) {
        if(index > list_size)
            return;

        ListNode* cur = sentrynode;
        ListNode* node = new ListNode(val);
        while(index--){
            cur = cur->next;
        }

        node->next = cur->next;
        cur->next = node;
        list_size++;
    }
    
    void deleteAtIndex(int index) {
        if(index >= list_size)
            return;

        ListNode* cur = sentrynode;
        while(index--){
            cur = cur->next;
        }

        ListNode* tmp = cur->next;
        cur->next = cur->next->next;
        delete tmp;
        list_size--;
    }
};

链表(置换)

反转链表(关键)

cpp 复制代码
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* pre = nullptr;
        ListNode* cur = head;

        while(cur){
            ListNode* nxt = cur->next;

            cur->next = pre;
            pre = cur;
            cur = nxt;
        }

        return pre;
    }

 ListNode* reverse_recu(ListNode* pre, ListNode* cur){
        if(!cur)
            return pre;

        ListNode* nxt = cur->next;

        cur->next = pre;

        return reverse_recu(cur, nxt);      //递归返回
    }

public:
    ListNode* reverseList2(ListNode* head) {
        return reverse_recu(nullptr, head);
    }
};

两两交换链表中的节点

cpp 复制代码
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* sentrynode = new ListNode(-1, head);

        ListNode* cur = sentrynode;

        while(cur->next && cur->next->next){

            ListNode* nxt = cur->next;
            ListNode* nxxt = cur->next->next->next;

            cur->next = cur->next->next;
            cur->next->next = nxt;
            nxt->next = nxxt;    //cur->next->next->next = nxxt;
            
            cur = cur->next->next; // 去到要交换的前驱
        }

        head = sentrynode->next;
        delete sentrynode;

        return head;
    }
};

删除链表的倒数第N个结点

cpp 复制代码
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* sentrynode = new ListNode(-1, head);
        ListNode* fast = sentrynode, *slow = sentrynode;

        while(n--){
            fast = fast->next;
        }

        while(fast->next){
            fast = fast->next;
            slow = slow->next;
        }
        
        ListNode* tmp = slow->next;
        //slow->next = fast;    //BUG
        slow->next = slow->next->next;
        delete tmp;

        return sentrynode->next;
    }

     ListNode* removeNthFromEnd2(ListNode* head, int n) {
        ListNode* sentrynode = new ListNode(-1, head);
        ListNode* movenode = sentrynode;
        stack<ListNode*> res;

        // 出栈不意味着断开链接
        while(movenode){
            res.push(movenode);
            movenode = movenode->next;

        }

        while(n > 0){
            res.pop();
            n--;
        }

        movenode = res.top();
        movenode->next = movenode->next->next;
        ListNode* ans  = sentrynode->next;
        delete sentrynode;

        return ans;

    }
};

链表(相交与环)

相交链表

cpp 复制代码
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode* l1 = headA;
        ListNode* l2 = headB;

        int len_headA = 0, len_headB = 0;

        while(l1){
            l1 = l1->next;
            len_headA++;
        }
        while(l2){
            l2 = l2->next;
            len_headB++;
        }

        int gap = abs(len_headA-len_headB);

        l1 = headA;
        l2 = headB;
        if(len_headA < len_headB){
            while(gap--){
                l2 = l2->next;
            }
        }else{
            while(gap--){
                l1 = l1->next;
            }
        }

        while(l1){
            if(l1 == l2){
                return l1;
            }else{
                l1 = l1->next;
                l2 = l2->next;
            }
        }

        return nullptr;
    }
};

环形链表

cpp 复制代码
class Solution {
public:
    bool hasCycle(ListNode *head) {
        if(head == NULL)
            return false;
        ListNode* slow = head;
        ListNode* fast = head;

        while(fast){
            if(fast->next)
                fast = fast->next->next;
            else
                return false;

            slow = slow->next;
            if(slow == fast)
                return true;
        }

        return false;
    }
};

环形链表II

cpp 复制代码
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* fast = head;
        ListNode* slow = head;

        while(fast && fast->next){      //循环判断条件 fast->next  不可少
            fast = fast->next->next;
            slow = slow->next;

            // (x + y) * 2 = x + y + n (y + z)
            if(slow == fast){
                slow = head;

                while(slow != fast){
                    slow = slow->next;
                    fast = fast->next;

                    // if(slow == fast)
                    //     return slow;    这样处理逻辑会超时
                }
                return slow;
            }
        }

        return nullptr;
    }
};

(可以使用哈希集合进行记录来寻找环入口)

二叉树

树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根节点加上两棵别称为左子树和右子树的二叉树组成。

根结点:根节点没有前驱结点。

除根节点外,其余结点被分成是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继。因此,树是递归定义的。

节点的度:一个节点含有的子树的个数称为该节点的度;

叶节点:度为0的节点称为叶节点;

非终端节点或分支节点:度不为0的节点;

双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;

孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点;

兄弟节点:具有相同父节点的节点互称为兄弟节点;

树的度:一棵树中,最大的节点的度称为树的度;

节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;

树的高度或深度:树中节点的最大层次;

堂兄弟节点:双亲在同一层的节点互为堂兄弟;

节点的祖先:从根到该节点所经分支上的所有节点;

子孙:以某节点为根的子树中任一节点都称为该节点的子孙;

森林:由m棵互不相交的树的集合称为森林;

二叉树的特点:

每个结点最多有两棵子树,即二叉树不存在度大于2的结点。

二叉树的子树有左右之分,其子树的次序不能颠倒。

定义(关键)

cpp 复制代码
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};

( 链式存储的二叉树是一种特殊的双链表结构,有着非常多的分类)

满二叉树:如果⼀棵⼆叉树只有度为0的结点和度为2的结点,并且度为0的结点在同⼀层上,则这棵⼆叉树为满⼆叉树 (深度为k,有2^k-1个节点的⼆叉树)

完全二叉树

二叉搜索树 :若它的左⼦树不空,则左⼦树上所有结点的值均⼩于它的根结点的值,若它的右⼦树不空,则右⼦树上所有结点的值均⼤于它的根结点的值,它的左、右⼦树也分别为⼆叉排序树 (中序遍历即可将其从小到达遍历一遍)

平衡二叉搜索树:AVL(Adelson-Velsky and Landis)树,它是⼀棵空树或它的左右两个⼦树的⾼度差的绝对值不超过1,并且左右两个⼦树都是⼀棵平衡⼆叉树

⼆叉树可以链式存储,也可以顺序存储(即链式存储⽅式就⽤指针, 顺序存储的⽅式就是⽤数组)

⽗节点的数组下标是 i,那么它的左孩⼦就是 i * 2 + 1,右孩⼦就是 i * 2 + 2

二叉树遍历

迭代写法(前序遍历)

因为要访问的元素和要处理的元素顺序是一致的,都是中间节点。

cpp 复制代码
class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        stack<TreeNode*> st;
        vector<int> result;
        if (root == NULL) return result;
        st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();                       // 中
            st.pop();
            result.push_back(node->val);
            if (node->right) st.push(node->right);           // 右(空节点不入栈)
            if (node->left) st.push(node->left);             // 左(空节点不入栈)
        }
        return result;
    }
};

递归写法(前序遍历)

cpp 复制代码
class Solution {
public:
    void traversal(TreeNode* cur, vector<int>& vec) {
        if (cur == NULL) return;
        vec.push_back(cur->val);    // 中
        traversal(cur->left, vec);  // 左
        traversal(cur->right, vec); // 右
    }
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> result;
        traversal(root, result);
        return result;
    }
};

递归写法(中序遍历)

cpp 复制代码
void traversal(TreeNode* cur, vector<int>& vec) {
    if (cur == NULL) return;
    traversal(cur->left, vec);  // 左
    vec.push_back(cur->val);    // 中
    traversal(cur->right, vec); // 右
}

递归写法(后序遍历)

cpp 复制代码
void traversal(TreeNode* cur, vector<int>& vec) {
    if (cur == NULL) return;
    traversal(cur->left, vec);  // 左
    traversal(cur->right, vec); // 右
    vec.push_back(cur->val);    // 中
}

迭代写法(中序遍历)

处理顺序和访问顺序是不一致的。在使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。

cpp 复制代码
class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> st;
        TreeNode* cur = root;
        while (cur != NULL || !st.empty()) {
            if (cur != NULL) { // 指针来访问节点,访问到最底层
                st.push(cur); // 将访问的节点放进栈
                cur = cur->left;                // 左
            } else {
                cur = st.top(); // 从栈里弹出的数据,就是要处理的数据(放进result数组里的数据)
                st.pop();
                result.push_back(cur->val);     // 中
                cur = cur->right;               // 右
            }
        }
        return result;
    }
};

迭代写法(后序遍历)

cpp 复制代码
class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        stack<TreeNode*> st;
        vector<int> result;
        if (root == NULL) return result;
        st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();
            st.pop();
            result.push_back(node->val);
            if (node->left) st.push(node->left); // 相对于前序遍历,这更改一下入栈顺序 (空节点不入栈)
            if (node->right) st.push(node->right); // 空节点不入栈
        }
        reverse(result.begin(), result.end()); // 将结果反转之后就是左右中的顺序了
        return result;
    }
};

统一写法

将访问的节点放入栈中,把要处理的节点也放入栈中但是要做标记。

NULL作标记表示访问过

cpp 复制代码
class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> st;
        if (root != NULL) st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();
            if (node != NULL) {
                st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
                if (node->right) st.push(node->right);  // 添加右节点(空节点不入栈)

                st.push(node);                          // 添加中节点
                st.push(NULL); // 中节点访问过,但是还没有处理,加入空节点做为标记。

                if (node->left) st.push(node->left);    // 添加左节点(空节点不入栈)
            } else { // 只有遇到空节点的时候,才将下一个节点放进结果集
                st.pop();           // 将空节点弹出
                node = st.top();    // 重新取出栈中元素
                st.pop();
                result.push_back(node->val); // 加入到结果集
            }
        }
        return result;
    }

    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> st;
        if (root != NULL) st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();
            if (node != NULL) {
                st.pop();
                if (node->right) st.push(node->right);  // 右
                if (node->left) st.push(node->left);    // 左
                st.push(node);                          // 中
                st.push(NULL);
            } else {
                st.pop();
                node = st.top();
                st.pop();
                result.push_back(node->val);
            }
        }
        return result;
    }

    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> st;
        if (root != NULL) st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();
            if (node != NULL) {
                st.pop();
                st.push(node);                          // 中
                st.push(NULL);

                if (node->right) st.push(node->right);  // 右
                if (node->left) st.push(node->left);    // 左

            } else {
                st.pop();
                node = st.top();
                st.pop();
                result.push_back(node->val);
            }
        }
        return result;
    }
};

层序遍历

迭代的方法使用队列数据结构进行记录

cpp 复制代码
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        queue<TreeNode*> que;
        if (root != NULL) que.push(root);
        vector<vector<int>> result;
        while (!que.empty()) {
            int size = que.size();  //计算广度大小
            vector<int> vec;
            // 这里一定要使用固定大小size,不要使用que.size(),因为que.size是不断变化的
            for (int i = 0; i < size; i++) {
                TreeNode* node = que.front();
                que.pop();
                vec.push_back(node->val);
                if (node->left) que.push(node->left);
                if (node->right) que.push(node->right);
            }
            result.push_back(vec);
        }
        return result;
    }

    void order(TreeNode* cur, vector<vector<int>>& result, int depth) # 递归法
    {
        if (cur == nullptr) return;
        if (result.size() == depth) result.push_back(vector<int>());
        result[depth].push_back(cur->val);
        order(cur->left, result, depth + 1);
        order(cur->right, result, depth + 1);
    }
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> result;
        int depth = 0;
        order(root, result, depth);
        return result;
    }
};

二叉树的右视图

cpp 复制代码
class Solution {
public:
    vector<int> rightSideView(TreeNode* root) {
        queue<TreeNode*> que;
        if (root != NULL) que.push(root);
        vector<int> result;
        while (!que.empty()) {
            int size = que.size();
            for (int i = 0; i < size; i++) {
                TreeNode* node = que.front();
                que.pop();
                if (i == (size - 1)) result.push_back(node->val); // 将每一层的最后元素放入result数组中
                if (node->left) que.push(node->left);
                if (node->right) que.push(node->right);
            }
        }
        return result;
    }
};

N叉树的层序遍历

cpp 复制代码
/*
// Definition for a Node.
class Node {
public:
    int val;
    vector<Node*> children;

    Node() {}

    Node(int _val) {
        val = _val;
    }

    Node(int _val, vector<Node*> _children) {
        val = _val;
        children = _children;
    }
};
*/
class Solution {
public:
    vector<vector<int>> levelOrder(Node* root) {
        queue<Node*> que;
        if (root != NULL) que.push(root);
        vector<vector<int>> result;
        while (!que.empty()) {
            int size = que.size();
            vector<int> vec;
            for (int i = 0; i < size; i++) {
                Node* node = que.front();
                que.pop();
                vec.push_back(node->val);
                for (int i = 0; i < node->children.size(); i++) { // 将节点孩子加入队列
                    if (node->children[i]) que.push(node->children[i]);
                }
            }
            result.push_back(vec);
        }
        return result;

    }
};

填充每个节点的下一个右侧节点指针II

cpp 复制代码
/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* left;
    Node* right;
    Node* next;

    Node() : val(0), left(NULL), right(NULL), next(NULL) {}

    Node(int _val) : val(_val), left(NULL), right(NULL), next(NULL) {}

    Node(int _val, Node* _left, Node* _right, Node* _next)
        : val(_val), left(_left), right(_right), next(_next) {}
};
*/

class Solution {
public:
    Node* connect(Node* root) {
        if(!root)
            return nullptr;

        queue<Node*> que;
        que.push(root);

        while(!que.empty()){
            int lay_len = que.size();
            Node* pre = nullptr, *cur = nullptr;
            
            for(int i = 0; i < lay_len; i++){
                Node* node = que.front();
                que.pop();
                cur = node;
                if(!pre){
                    pre = cur;
                }else{
                    pre->next = cur;
                    pre = cur;
                }
                if(node->left)  que.push(node->left);
                if(node->right)  que.push(node->right);
            }

        }
        return root;
    }

    
    void handle(Node* &last, Node* &p, Node* &nextStart){
            if(last){
                last->next = p;
            }

            if(!nextStart){    //为空才跟踪p
                nextStart = p;
            }

            last = p;    //一直跟踪p
        }  //引用是指针常量

    Node* connect(Node* root) {
        if(!root)
            return nullptr;

        Node* start = root;

        while(start){
            Node* last = nullptr, *nextStart = nullptr;
            for(Node* p = start; p != nullptr; p = p->next){
                if(p->left)
                    handle(last, p->left, nextStart);
                if(p->right)
                    handle(last, p->right, nextStart);
            }

            start = nextStart;
        }

        return root;
    }
};

二叉树的属性

对称二叉树

要比较的可不是左右节点!其实我们要比较的是两个树(这两个树是根节点的左右子树)

cpp 复制代码
class Solution {
public:
    bool compare(TreeNode* left, TreeNode* right) {
        if (left == NULL && right != NULL) return false;
        else if (left != NULL && right == NULL) return false;
        else if (left == NULL && right == NULL) return true;
        else if (left->val != right->val) return false;
        else return compare(left->left, right->right) && compare(left->right, right->left);

    }
    bool isSymmetric(TreeNode* root) {
        if (root == NULL) return true;
        return compare(root->left, root->right);
    }

    
    bool isSymmetric2(TreeNode* root) {
        if (root == NULL) return true;
        queue<TreeNode*> que;
        que.push(root->left);   // 将左子树头结点加入队列
        que.push(root->right);  // 将右子树头结点加入队列
        
        while (!que.empty()) {  // 接下来就要判断这两个树是否相互翻转
            TreeNode* leftNode = que.front(); que.pop();
            TreeNode* rightNode = que.front(); que.pop();
            if (!leftNode && !rightNode) {  // 左节点为空、右节点为空,此时说明是对称的
                continue;
            }

            // 左右一个节点不为空,或者都不为空但数值不相同,返回false
            if ((!leftNode || !rightNode || (leftNode->val != rightNode->val))) {
                return false;
            }
            que.push(leftNode->left);   // 加入左节点左孩子
            que.push(rightNode->right); // 加入右节点右孩子
            que.push(leftNode->right);  // 加入左节点右孩子
            que.push(rightNode->left);  // 加入右节点左孩子
        }
        return true;
    }
};

完全二叉树的节点个数

cpp 复制代码
class Solution {
public:
    int countNodes(TreeNode* root) {
        if (root == nullptr) return 0;
        TreeNode* left = root->left;
        TreeNode* right = root->right;
        int leftDepth = 0, rightDepth = 0; // 这里初始为0是有目的的,为了下面求指数方便
        while (left) {  // 求左子树深度
            left = left->left;
            leftDepth++;
        }
        while (right) { // 求右子树深度
            right = right->right;
            rightDepth++;
        }
        if (leftDepth == rightDepth) {
            return (2 << leftDepth) - 1; // 注意(2<<1) 相当于2^2,所以leftDepth初始为0
        }
        return countNodes(root->left) + countNodes(root->right) + 1;
    }
};

平衡二叉树

高度,必然是要后序遍历

cpp 复制代码
class Solution {
public:
    // 返回以该节点为根节点的二叉树的高度,如果不是平衡二叉树了则返回-1
    int getHeight(TreeNode* node) {
        if (node == NULL) {
            return 0;
        }
        int leftHeight = getHeight(node->left);
        if (leftHeight == -1) return -1;
        int rightHeight = getHeight(node->right);
        if (rightHeight == -1) return -1;
        return abs(leftHeight - rightHeight) > 1 ? -1 : 1 + max(leftHeight, rightHeight);
    }
    bool isBalanced(TreeNode* root) {
        return getHeight(root) == -1 ? false : true;
    }
};

二叉树的所有路径

cpp 复制代码
class Solution {
    private:
        void dfs(TreeNode* root, string stringpath, vector<string>& res)  //回溯 不用 引用传参
        {
            stringpath += to_string(root->val);

            if(root->left == nullptr && root->right == nullptr)
            {
                res.push_back(stringpath);

                return;
            }

            if(root->left)
            {
                stringpath += "->";
                dfs(root->left, stringpath, res);
                stringpath.pop_back();     // 回溯 >
                stringpath.pop_back();     // 回溯 -

            }

            if(root->right)
            {
                stringpath += "->";
                dfs(root->right, stringpath, res);
                stringpath.pop_back();
                stringpath.pop_back();
            }
        }
public:
    vector<string> binaryTreePaths(TreeNode* root) {
        vector<string> res;

        if(!root)
            return res;

        dfs(root, "", res);

        return res;
    }
};

(如果用栈模拟,递归(系统调用栈)中,函数所处理的局部变量都需要使用栈来存储)

左叶子之和

判断左叶子,不是二叉树左侧节点!!!!!! (后序遍历)

cpp 复制代码
class Solution {
public:
    int sumOfLeftLeaves(TreeNode* root) {
        if (root == NULL) return 0;
        if (root->left == NULL && root->right== NULL) return 0;

        int leftValue = sumOfLeftLeaves(root->left);    // 左
        
        int rightValue = sumOfLeftLeaves(root->right);  // 右

        if (root->left && !root->left->left && !root->left->right) { // 左子树就是一个左叶子的情况
            leftValue = root->left->val;
        }

        int sum = leftValue + rightValue;               // 中
        return sum;
    }
};

找树左下角的值

(使用层序遍历很好想地解决问题)

cpp 复制代码
class Solution {
public:
    int maxDepth = INT_MIN;
    int result;
    void traversal(TreeNode* root, int depth) {
        if (root->left == NULL && root->right == NULL) {
            if (depth > maxDepth) {
                maxDepth = depth;
                result = root->val;
            }
            return;
        }
        if (root->left) {
            traversal(root->left, depth + 1); // 隐藏着回溯
        }
        if (root->right) {
            traversal(root->right, depth + 1); // 隐藏着回溯
        }
        return;
    }
    int findBottomLeftValue(TreeNode* root) {
        traversal(root, 0);
        return result;
    }
};

二叉树的修改与构造

构建二叉树 推荐 数组索引 + 左闭右开划分

从中序与后序遍历序列构造二叉树

cpp 复制代码
class Solution {
    public:
        //左闭右开
        TreeNode* sub_build(vector<int>& inorder, vector<int>& postorder, int in_left, int in_right, 
        int post_left, int post_right){
            
            if(post_left == post_right) return nullptr;

            int rootvalue = postorder[post_right-1];
            TreeNode* root = new TreeNode(rootvalue);

            if(post_left == post_right-1) return root;

            int index;
            for(index = in_left; index < in_right; index++){
                if(inorder[index] == rootvalue){
                    break;
                }
            }

            //inorder
            int in_left_begin = in_left;
            int in_left_end = index;
            int in_right_begin = index+1;  //中取出 
            int in_right_end = in_right;
            //postorder
            int post_left_begin = post_left;
            int post_left_end = post_left + (index - in_left);
            int post_right_begin = post_left + (index - in_left);
            int post_right_end = post_right-1;   //中取出

            root->left = sub_build(inorder, postorder, in_left_begin, in_left_end, post_left_begin, post_left_end);
            root->right = sub_build(inorder, postorder, in_right_begin, in_right_end, post_right_begin, post_right_end);
        
            return root;
        }
public:
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        if(inorder.size() == 0 || postorder.size() == 0)
            return nullptr;

        return sub_build(inorder, postorder, 0, inorder.size(), 0, postorder.size());
    }
};

合并二叉树

cpp 复制代码
class Solution {
public:
    TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
        if(!root1 && !root2) return nullptr;
        else if(!root1 )
            return root2;
        else if(!root2)
            return root1;
    
        root1->val += root2->val;

        root1->left = mergeTrees(root1->left, root2->left);
        root1->right = mergeTrees(root1->right, root2->right);

        return root1;
    }
};

二叉搜索树的属性

二叉搜索树中的搜索

cpp 复制代码
class Solution {
public:
    TreeNode* searchBST(TreeNode* root, int val) {
        if (root == NULL || root->val == val) return root;
        if (root->val > val) return searchBST(root->left, val);
        if (root->val < val) return searchBST(root->right, val);
        return NULL;
    }
};

验证二叉搜索树

中序遍历下,输出的二叉搜索树节点的数值是有序序列

验证二叉搜索树,就相当于变成了判断一个序列是不是递增的了

(二叉搜索树也可以为空!)

cpp 复制代码
class Solution {
public:
    long long maxVal = LONG_MIN; // 因为后台测试数据中有int最小值
    bool isValidBST(TreeNode* root) {
        if (root == NULL) return true;

        bool left = isValidBST(root->left);
        // 中序遍历,验证遍历的元素是不是从小到大
        if (maxVal < root->val) maxVal = root->val;
        else return false;
        bool right = isValidBST(root->right);

        return left && right;
    }
};

二叉搜索树的最小绝对差

在中序遍历下处理

cpp 复制代码
class Solution {
    private:
        int result = INT_MAX;
        TreeNode* pre = NULL;
        void traversal(TreeNode* cur) {
            if (cur == NULL) return;
            traversal(cur->left);   // 左
            if (pre != NULL){       // 中
                result = min(result, cur->val - pre->val);
            }
            pre = cur; // 记录前一个
            traversal(cur->right);  // 右
        }
public:
    int getMinimumDifference(TreeNode* root) {
        traversal(root);
        return result;
    }
};

把二叉搜索树转换为累加树

逆向中序遍历

cpp 复制代码
class Solution {
private:
    int pre = 0; // 记录前一个节点的数值
    void traversal(TreeNode* cur) { // 右中左遍历
        if (cur == NULL) return;
        traversal(cur->right);
        cur->val += pre;
        pre = cur->val;
        traversal(cur->left);
    }
public:
    TreeNode* convertBST(TreeNode* root) {
        pre = 0;
        traversal(root);
        return root;
    }
};

二叉树公共祖先

二叉树的最近公共祖先

自底向上查找 后序遍历(左右中)就是天然的回溯过程,可以根据左右子树的返回值,来处理中节点的逻辑

cpp 复制代码
一条边
if (递归函数(root->left)) return ;

if (递归函数(root->right)) return ;

/**********************************************/

整棵树
left = 递归函数(root->left);  // 左
right = 递归函数(root->right); // 右
left与right的逻辑处理;         // 中 

在递归函数有返回值的情况下:如果要搜索一条边,递归函数返回值不为空的时候,立刻返回,如果搜索整个树,直接用一个变量left、right接住返回值,这个left、right后序还有逻辑处理的需要,也就是后序遍历中处理中间节点的逻辑(也是回溯)

cpp 复制代码
class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if (root == q || root == p || root == NULL) return root;
        TreeNode* left = lowestCommonAncestor(root->left, p, q);
        TreeNode* right = lowestCommonAncestor(root->right, p, q);
        if (left != NULL && right != NULL) return root;  // 此时两个节点分别位于左右两侧
        if (left == NULL) return right;    // 此时两个节点都在右侧
        return left;                       // 此时两个节点都在左侧
    }
};

二叉搜索树的最近公共祖先

前序遍历

cpp 复制代码
class Solution {
    private:
    TreeNode* traversal(TreeNode* cur, TreeNode* p, TreeNode* q) {
        if (cur == p || cur == q || cur == NULL) return cur;
                                                        // 中
        if (cur->val > p->val && cur->val > q->val) {   // 左
            TreeNode* left = traversal(cur->left, p, q);
            if (left != NULL) {
                return left;
            }
        }

        if (cur->val < p->val && cur->val < q->val) {   // 右
            TreeNode* right = traversal(cur->right, p, q);
            if (right != NULL) {
                return right;
            }
        }
        return cur;
    }
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        return traversal(root, p, q);
    }

    TreeNode* lowestCommonAncestor2(TreeNode* root, TreeNode* p, TreeNode* q) {
        if (root->val > p->val && root->val > q->val) {
            return lowestCommonAncestor2(root->left, p, q);
        } else if (root->val < p->val && root->val < q->val) {
            return lowestCommonAncestor2(root->right, p, q);
        } else return root;
    }
};

二叉搜索树的修改与构造

二叉搜索树中的插入操作

前序遍历 接住节点

cpp 复制代码
class Solution {
public:
    TreeNode* insertIntoBST(TreeNode* root, int val) {
        if (root == NULL) {
            TreeNode* node = new TreeNode(val);
            return node;  //返回插入节点
        }
        if (root->val > val) root->left = insertIntoBST(root->left, val);
        if (root->val < val) root->right = insertIntoBST(root->right, val);
        return root;    //递归返回的最后根节点
    }
};

删除二叉搜索树中的节点

前序遍历 接住节点

cpp 复制代码
class Solution {
public:
    TreeNode* deleteNode(TreeNode* root, int key) {
        if (root == nullptr) return root; // 第一种情况:没找到删除的节点,遍历到空节点直接返回了
        if (root->val == key) {
            // 第二种情况:左右孩子都为空(叶子节点),直接删除节点, 返回NULL为根节点
            if (root->left == nullptr && root->right == nullptr) {
                ///! 内存释放
                delete root;
                return nullptr;
            }
            // 第三种情况:其左孩子为空,右孩子不为空,删除节点,右孩子补位 ,返回右孩子为根节点
            else if (root->left == nullptr) {
                auto retNode = root->right;
                ///! 内存释放
                delete root;
                return retNode;
            }
            // 第四种情况:其右孩子为空,左孩子不为空,删除节点,左孩子补位,返回左孩子为根节点
            else if (root->right == nullptr) {
                auto retNode = root->left;
                ///! 内存释放
                delete root;
                return retNode;
            }
            // 第五种情况:左右孩子节点都不为空,则将删除节点的左子树放到删除节点的右子树的最左面节点的左孩子的位置
            // 并返回删除节点右孩子为新的根节点。
            else {
                TreeNode* cur = root->right; // 找右子树最左面的节点
                while(cur->left != nullptr) {
                    cur = cur->left;
                }
                cur->left = root->left; // 把要删除的节点(root)左子树放在cur的左孩子的位置
                TreeNode* tmp = root;   // 把root节点保存一下,下面来删除
                root = root->right;     // 返回旧root的右孩子作为新root
                delete tmp;             // 释放节点内存(这里不写也可以,但C++最好手动释放一下吧)
                return root;
            }
        }
        if (root->val > key) root->left = deleteNode(root->left, key);
        if (root->val < key) root->right = deleteNode(root->right, key);
        return root;
    }
};

因为二叉搜索树添加节点只需要在叶子上添加就可以的,不涉及到结构的调整,而删除节点操作涉及到结构的调整

修剪二叉搜索树

修剪的操作并不是在终止条件上进行的,即遇到空节点返回就可以了 (前序遍历)

cpp 复制代码
class Solution {
public:
    TreeNode* trimBST(TreeNode* root, int low, int high) {
        if (root == nullptr ) return root;
        if (root->val < low) {
            TreeNode* right = trimBST(root->right, low, high); // 寻找符合区间[low, high]的节点
            return right;
        }
        if (root->val > high) {
            TreeNode* left = trimBST(root->left, low, high); // 寻找符合区间[low, high]的节点
            return left;
        }
        root->left = trimBST(root->left, low, high); // root->left接入符合条件的左孩子
        root->right = trimBST(root->right, low, high); // root->right接入符合条件的右孩子
        return root;
    }

    TreeNode* trimBST2(TreeNode* root, int low, int high) {
        if (root == nullptr) return root;
        if (root->val < low) return trimBST(root->right, low, high);   //修剪
        if (root->val > high) return trimBST(root->left, low, high);

        root->left = trimBST(root->left, low, high);    //接住
        root->right = trimBST(root->right, low, high);
        
        return root;  //递归返回  
    }
};

将有序数组转换为二叉搜索树

前序遍历

cpp 复制代码
class Solution {
private:
    TreeNode* traversal(vector<int>& nums, int left, int right) {
        if (left > right) return nullptr;
        int mid = left + ((right - left) / 2);
        TreeNode* root = new TreeNode(nums[mid]);
        root->left = traversal(nums, left, mid - 1);
        root->right = traversal(nums, mid + 1, right);
        return root;
    }
public:
    TreeNode* sortedArrayToBST(vector<int>& nums) {
        TreeNode* root = traversal(nums, 0, nums.size() - 1);
        return root;
    }
};
相关推荐
电子云与长程纠缠22 分钟前
UE5编辑器下将RenderTarget输出为UTexture并保存
学习·ue5·编辑器·虚幻
yeah__ii33 分钟前
周期字符串!
数据结构·c++·算法
鸿儒51734 分钟前
C++ QT 无边框设计之<拖拽窗体>
c++·qt
爱看书的小沐1 小时前
【小沐学GIS】基于C++绘制三维数字地球Earth(OpenGL、glfw、glut、QT)第三期
c++·qt·opengl·earth·osm·三维地球·数字地球
高高要努力1 小时前
【面试】java多线程
java·面试
鳄鱼皮坡1 小时前
【linux系统】基础开发工具(yum、Vim)
linux·c++·ubuntu
人才程序员1 小时前
ffmpeg C语音 读取视频帧源码
c语言·c++·ffmpeg·音视频·webrtc·实时音视频·视频编解码
艾妮艾妮1 小时前
进制的转换
c语言·开发语言·数据结构·python
菜鸟起航ing1 小时前
数据结构---哈希表(Hash Table)
java·数据结构·哈希算法·散列表
郝YH是人间理想1 小时前
某名校考研自命题C++程序设计——近10年真题汇总(上)
开发语言·c++·考研