leetcode 树

本文详细介绍了二叉树的各种遍历方法(前序、中序、后序、层序)的递归和非递归实现,并涵盖了二叉搜索树的验证、相同树的判断、对称树的判断、平衡二叉树的检测、路径和统计等操作。此外,还讨论了N叉树的层序遍历以及二叉树的构造、序列化和反序列化问题。
摘要由CSDN通过智能技术生成

不定时更新,收藏即可

遍历

层序遍历即宽搜,前中后序遍历即深搜

二叉树的前序遍历

递归

vector<int> res;
vector<int> preorderTraversal(TreeNode* root) {
    dfs(root);
    return res;
}
void dfs(TreeNode* root) {
    if(!root) return;
    res.push_back(root->val);
    dfs(root->left);        
    dfs(root->right);
}

非递归

vector<int> preorderTraversal(TreeNode* root) {
    vector<int> res;
    if (!root) return res;
    
    stack<TreeNode*> stk;
    stk.push(root);
    while (stk.size()) {
        root = stk.top(); stk.pop();
        res.push_back(root->val);
        if (root->right) stk.push(root->right);
        if (root->left) stk.push(root->left);
    }
    
    return res;
}

二叉树的中序遍历

递归

vector<int> res;
vector<int> inorderTraversal(TreeNode* root) {
    dfs(root);
    return res;
}
void dfs(TreeNode* root) {
    if(!root) return;
    dfs(root->left);
    res.push_back(root->val);
    dfs(root->right);
}

非递归

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

二叉树的后序遍历

递归

vector<int> res;
vector<int> postorderTraversal(TreeNode* root) {
    dfs(root);
    return res;
}
void dfs(TreeNode* root) {
    if(!root) return;
    dfs(root->left);
    dfs(root->right);
    res.push_back(root->val);
}

非递归

vector<int> postorderTraversal(TreeNode* root) {
    vector<int> res;
    if (!root) return res;
    
    stack<TreeNode*> s1, s2;
    s1.push(root);
    while (s1.size()) {
        root = s1.top(); s1.pop();
        if (root->left) s1.push(root->left);
        if (root->right) s1.push(root->right);
        s2.push(root);
    }     
    while (s2.size()) res.push_back(s2.top()->val), s2.pop();
    
    return res;
}

二叉树的层序遍历

自顶向下层序遍历

vector<vector<int>> levelOrder(TreeNode* root) {
    vector<vector<int>> res;
    if (!root) return res;

    queue<TreeNode*> q;
    q.push(root);
    while (q.size()) {
        vector<int> level;
        int len = q.size();
        while (len--) {
            auto x = q.front(); q.pop();
            level.push_back(x->val);
            if (x->left) q.push(x->left);
            if (x->right) q.push(x->right);
        }
        res.push_back(level);
    }

    return res;
}

自底向上层序遍历

vector<vector<int>> levelOrderBottom(TreeNode* root) {
    vector<vector<int>> res;
    if (!root) return res;

    queue<TreeNode*> q;
    q.push(root);
    while (q.size()) {
        vector<int> level;
        int len = q.size();
        while (len--) {
            auto x = q.front(); q.pop();
            level.push_back(x->val);
            if (x->left) q.push(x->left);
            if (x->right) q.push(x->right);
        }
        res.push_back(level);
    }
    reverse(res.begin(), res.end());

    return res;
}

锯齿形层序遍历

vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
    vector<vector<int>> res;
    if (!root) return res;

    queue<TreeNode*> q; q.push(root);
    bool st = false;
    while (q.size()) {
        vector<int> level;
        int len = q.size();
        while (len--) {
            auto x = q.front(); q.pop();
            level.push_back(x->val);
            if (x->left) q.push(x->left);
            if (x->right) q.push(x->right);
        }
        if (st) reverse(level.begin(), level.end());
        st = !st;
        res.push_back(level);
    }
    return res;
}

N 叉树的层序遍历

跟二叉树的层序遍历一毛一样,本质都是把所有儿子塞进去

  • 二叉树只有两个子节点,所以只要塞左右儿子就行

  • N 叉树具有多个子节点,所以要塞入 N 个儿子

vector<vector<int>> levelOrder(Node* root) {
    vector<vector<int>> res;
    if (!root) return res;

    queue<Node*> q;
    q.push(root);
    while (q.size()) {
        vector<int> level;
        int len = q.size();
        while (len--) {
            auto x = q.front(); q.pop();
            level.push_back(x->val);
            for (auto child : x->children) q.push(child); // 塞入所有儿子
        }
        res.push_back(level);
    }

    return res;
}

验证

验证二叉搜索树

法一:定义

利用二叉搜索树定义判断,即左小、右大、左右均是二叉搜索树

typedef long long LL;

bool isValidBST(TreeNode* root) {
    return dfs(root, LONG_MIN, LONG_MAX);
}

bool dfs(TreeNode* root, LL l, LL r) {
    if (!root) return true;
    LL x = root->val;
    if (x < l || x > r) return false;
    return x > l && x < r 
        && dfs(root->left, l , x) && dfs(root->right, x, r);
}

法二:性质

利用二叉搜索树性质判断,即二叉搜索中序遍历是一个升序序列

long long pre = LONG_MIN;
bool res = true;

bool isValidBST(TreeNode* root) {
    dfs(root);
    return res;
}

void dfs(TreeNode* root) {
    if (!root) return;
    dfs(root->left);
    if (root->val <= pre) res = false;
    pre = root->val;
    dfs(root->right);
}

相同的树

同时对两颗树进行遍历

  1. 如果一棵树遍历完了,另外一棵还有节点。那么一定不相同
  2. 遍历时两个数的节点的值不同。那么一定不相同
bool isSameTree(TreeNode* p, TreeNode* q) {
    if (!p || !q) return q == p;
    if (p->val != q->val) return false;
    return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}

对称二叉树

将一棵树当作两棵树进行上一题的操作

bool isSymmetric(TreeNode* root) {
    return isSameTree(root, root);
}

bool isSameTree(TreeNode* p, TreeNode* q) {
    if (!p || !q) return q == p;
    if (p->val != q->val) return false;
    return isSameTree(p->left, q->right) && isSameTree(p->right, q->left); // 注意,两个二叉树是相反的
}

平衡二叉树

// 按照定义去做,如果不满足定义直接 -1 标记,满足则返回高度即可
bool isBalanced(TreeNode* root) {
    return dfs(root) != -1;
}
int dfs(TreeNode* root) {
    if (!root) return 0;
    int left = dfs(root->left), right = dfs(root->right);
    if (left == -1 || right == -1 || abs(left - right) > 1) return -1;
    return max(left, right) + 1;
}

奇偶树

bool isEvenOddTree(TreeNode* root) {
    queue<TreeNode*> q;;
    q.push(root);
    bool odd = false;
    while (q.size()) {
        auto x = q.front();
        int pre = odd ? INT_MAX : INT_MIN;
        for (int i = q.size(); i; i--) {
            x = q.front(); q.pop();
            if (x->left) q.push(x->left);
            if (x->right) q.push(x->right);
            if (odd) {
                if (pre <= x->val || x->val & 1)
                    return false;
            } else {
                if (pre >= x->val || !(x->val & 1))  
                    return false;
            }
            pre = x->val;
        }
        odd = !odd;
    }
    return true;
}

统计

二叉树的最大深度

int maxDepth(TreeNode* root) {
    if (!root) return 0;
    return max(maxDepth(root->left), maxDepth(root->right)) + 1; // 左子树和右子树最大深度+1
}	

二叉树的最小深度

int minDepth(TreeNode* root) {
    if (!root) return 0;	
    if (!root->left && !root->right) return 1;
    int res = INT_MAX;
    if (root->left) res = min(res, minDepth(root->left));
    if (root->right) res = min(res, minDepth(root->right));
    return res + 1;
}

完全二叉树的节点个数

二分

  1. 左右是完全二叉树,直接算 2 h − 1 2^h - 1 2h1
  2. 左右都是满二叉树,直接算 左 + 右 + 1 左+右 +1 ++1
  3. 左右有一个不是满二叉树,则只需要递归非满二叉树部分
int countNodes(TreeNode* root) {
    if (!root) return 0;
    auto left = root->left, right = root->right;
    int l = 1, r = 1;
    while (left) left = left->left, l++;
    while (right) right = right->right, r++;
    if (l == r) return (1 << l) - 1;
    return countNodes(root->left) + countNodes(root->right) + 1;
}

二叉搜索树中第K小的元素

左小右大的二叉搜索树,中序遍历一定是升序的,所以只要返回中序遍历的第 K 个元素即可

int kthSmallest(TreeNode* root, int k) {
    dfs(root, k);
    return res;
}
int res;
bool dfs(TreeNode* root, int& k) { // 返回类型标记是否找到,找到了直接剪掉即可
    if (!root) return false;
    if (dfs(root->left, k)) return true;
    if (--k == 0) {
        res = root->val;
        return true;
    }
    if (dfs(root->right, k)) return true;
    return false;
}

左叶子之和

直接遍历统计,当为左叶子时才计数

左叶子:是左子树,且无左右子树

int sumOfLeftLeaves(TreeNode* root) {
    dfs(root);
    return res;
}
int res;
void dfs(TreeNode* root) {
    if (!root) return;
    if (root->left) 
        if (!root->left->right && !root->left->left) res += root->left->val;
    dfs(root->left), dfs(root->right);
}

二叉搜索树中的众数

二叉搜索中序遍历是有序的,所以相同的数会聚集在一块

vector<int> findMode(TreeNode* root) {
    dfs(root);
    return res;
}
vector<int> res;
int pre, cnt, maxv;
void dfs(TreeNode* root) {
    if (!root) return;
    dfs(root->left);
    if (!cnt || pre == root->val) cnt++;
    else cnt = 1;
    pre = root->val;         
    if (cnt > maxv) maxv = cnt, res = {pre};
    else if (cnt == maxv) res.push_back(pre);
    dfs(root->right);
}

找树左下角的值

宽搜,按行拿第一个

int findBottomLeftValue(TreeNode* root) {
    int res;
    queue<TreeNode*> q;
    q.push(root);
    while(!q.empty()){
        auto x = q.front();
        res = x->val;
        for(int i = q.size(); i; i--){
            x = q.front(); q.pop();
            if(x->left) q.push(x->left);
            if(x->right) q.push(x->right);
        }
    }
    return res;
}

深搜,先左再右,则左绝对是第一个在该层第一个搜索到的

int findBottomLeftValue(TreeNode* root) {
    dfs(root, 1);
    return res;
}
int res, maxv;
void dfs(TreeNode* root, int d) {
    if (!root) return;
    if (d > maxv) {
        maxv = d;
        res = root->val;
    }
    dfs(root->left, d + 1), dfs(root->right, d + 1);
}

在每个树行中找最大值

宽搜,和上题一毛一样的思路

vector<int> largestValues(TreeNode* root) {
    vector<int> res;
    if (!root) return res;

    queue<TreeNode*> q;
    q.push(root);        
    while(q.size()) {
        auto x = q.front();
        int v = INT_MIN;
        for (int i = q.size(); i; i--) {
            x = q.front(); q.pop();
            v = max(v, x->val);
            if (x->left) q.push(x->left);
            if (x->right) q.push(x->right);
        }
        res.push_back(v);
    }
    
    return res;
}

不同的二叉搜索树的数量

这是个结论,卡特兰数,记住就好

// 卡特兰数通项公式:C0 = 1, Cn+1 = 2 * (2n + 1) / (n + 2) * Cn
int numTrees(int n) {
    int C = 1;
    for (int i = 0; i < n; ++i) {
        C = 1L * C * 2 * (2 * i + 1) / (i + 2);
    }
    return C;
}

打家劫舍 III

int rob(TreeNode* root) {
    auto res = dfs(root);
    return max(res.first, res.second);
}
// 方案一:偷了 -> 下层不能偷
// 方案二:不偷 -> 下层能偷,按偷最多钱的方案偷
typedef pair<int, int> PII;
PII dfs(TreeNode* root) {
    if (!root) return {0, 0};
    auto l = dfs(root->left);
    auto r = dfs(root->right);
	
    // 方案一
    int first = root->val + l.second + r.second;
    // 方案二
    int second = max(l.first, l.second) + max(r.first, r.second);

    return {first, second};
}

路径

路径总和

遍历,每次目标值减去当前值。当没有子节点时,判断目标值是否刚好被剪光。只要有一条路径满足即可

bool hasPathSum(TreeNode* root, int t) {
    if (!root) return false;
    t -= root->val;
    if (!root->left && !root->right) return !t;
    return hasPathSum(root->right, t) || hasPathSum(root->left, t);
}

路径总和 II

深搜即可

vector<int> path;
vector<vector<int>> res;
vector<vector<int>> pathSum(TreeNode* root, int t) {
    dfs(root, t);
    return res;
}
void dfs(TreeNode* root, int t) {
    if (!root) return;
    path.push_back(root->val);
    t -= root->val;
    if (!root->left && !root->right && !t) res.push_back(path);
    dfs(root->left, t), dfs(root->right, t);
    path.pop_back(); // 恢复现场
}

路径总和 III

这个路径是不拐弯的

前缀和 + 哈希表优化。和这道题一毛一样的思想 和为K的子数组

前缀和 s[i] - s[j] = k 等价变形 s[j] = s[i] - k,只需要将 s[j] 作为 key 存入哈希表中,其 value 就是满足 s[j] = s[i] - k 等式的个数

unordered_map<int, int> map;
int res, sum;
int pathSum(TreeNode* root, int targetSum) {
    map[0] = 1, sum = targetSum;
    dfs(root, 0);
    return res;
}
void dfs(TreeNode* root, int cur) {
    if (!root)  return;
    cur += root->val;
    res += map[cur - sum];
    map[cur]++;
    dfs(root->left, cur), dfs(root->right, cur);
    map[cur]--;
}

二叉树中的最大路径和

每个节点都会产生最大值,但是最大值一定是左边最大+右边最大+当前值,所以只需要遍历到每个节点时抓一下。后序遍历是先到两个子节点再到根节点,所以用后序遍历汇总

int maxPathSum(TreeNode* root) {
    dfs(root);
    return res;
}
int res = INT_MIN; // 抓答案
int dfs(TreeNode* root) {
    if (!root) return 0;
    int left = max(dfs(root->left), 0);
    int right = max(dfs(root->right), 0);
    res = max(left + right + root->val, res);
    return max(left, right) + root->val;
}

构造

二叉树展开为链表

在这里插入图片描述

// 有左儿子,跑到左儿子的最右记为 p
//	1. p->right = root->right
//	2. root->right = p->left
//	3. root->left = nulll
// 跳到下一个左儿子继续
void flatten(TreeNode* root) {
    while (root) {
        auto p = root->left;
        if (p) {
            while (p->right) p = p->right;
            p->right = root->right;
            root->right = root->left;
            root->left = nullptr;
        }
        root = root->right;
    }
}

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

TreeNode* sortedArrayToBST(vector<int>& a) {
    int n = a.size();
    if (!n) return nullptr;
    return dfs(a, 0, n - 1);
}
TreeNode* dfs(vector<int>& a, int l, int r) {
    if (l > r) return nullptr;
    int mid = l + r + 1>> 1;
    auto root = new TreeNode();
    root->left = dfs(a, l, mid - 1);
    root->val = a[mid];
    root->right = dfs(a, mid + 1, r);
    return root;
}

有序链表转换二叉搜索树

中序建树 + 二分找中间节点

TreeNode* sortedListToBST(ListNode* head) {
    if (!head) return nullptr;
    return dfs(head, 0, getLen(head) - 1);
}
int getLen(ListNode* head) {
    int len = 0;
    while (head) head = head->next, len++;
    return len;	
}
TreeNode* dfs(ListNode* &head, int l, int r) {
    if (l > r) return nullptr;
    int mid = l + r + 1>> 1;
    auto root = new TreeNode();
    root->left = dfs(head, l, mid - 1);
    root->val = head->val, head = head->next;
    root->right = dfs(head, mid + 1, r);
    return root;
}

翻转二叉树

利用后序遍历特性,即先访问子再访问根

TreeNode* invertTree(TreeNode* root) {
    if (!root) return nullptr;
    auto l = invertTree(root->left), r = invertTree(root->right);
    root->left = r, root->right = l;
    return root;
}

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

给的是一个满二叉树,所以只需要从每一层的最左节点开始将 next 指向好就行

Node* connect(Node* root) {
    if (!root) return root;
    for (auto cur = root; cur->left; cur = cur->left) {
        for (auto p = cur; p; p = p->next) {
            p->left->next = p->right;
            if(p->next) p->right->next = p->next->left;
        }
    }
    return root;
}

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

遍历到这一层时自己维护下一层的链表

Node* connect(Node* root) {
    if (!root) return root;
    auto cur = root;
    while (cur) {
        auto head = new Node(), tail = head;
        for (auto p = cur; p; p = p->next) {
            if (p->left) tail = tail->next = p->left;
            if (p->right) tail = tail->next = p->right;
        }
        cur = head->next;
    }
    return root;
}

序列化

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

前序第一个元素一定是头。然后跑到中序序列找到头的位置,在其头的左边是左子树所有元素,在其头的右边是右子树所有元素

unordered_map<int, int> pos;
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
    int n = inorder.size(), m = preorder.size();
    for (int i = 0; i < n; i++) pos[inorder[i]] = i;
    return dfs(preorder, inorder, 0, m - 1, 0, n - 1);
}
TreeNode* dfs(vector<int>& preorder, vector<int>& inorder, int pl, int pr, int il, int ir) {
    if (pl > pr) return nullptr;
    auto root = new TreeNode(preorder[pl]);
    int k = pos[root->val];
    root->left = dfs(preorder, inorder, pl + 1, pl + 1 + k - 1 - il, il, k - 1);
    root->right = dfs(preorder, inorder, pl + 1 + k - 1 - il + 1, pr, k + 1, ir);
    return root;
}

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

后序最后一个元素一定是头。然后跑到中序序列找到头的位置,在其头的左边是左子树所有元素,在其头的右边是右子树所有元素

unordered_map<int, int> pos;
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
    int n = inorder.size(), m = postorder.size();
    for (int i = 0; i < n; i++) pos[inorder[i]] = i;
    return dfs(inorder, postorder, 0, n - 1, 0, m - 1);
}
TreeNode* dfs(vector<int>& inorder, vector<int>& postorder, int il, int ir, int pl, int pr) {
    if (pl > pr) return nullptr;
    auto root = new TreeNode(postorder[pr]);
    int k = pos[root->val];
    root->left = dfs(inorder, postorder, il, k - 1, pl, pl + k - 1 - il);
    root->right = dfs(inorder, postorder, k + 1, ir, pl + k - 1 - il + 1, pr - 1);
    return root;
}

验证二叉树的前序序列化

消耗插槽法

bool isValidSerialization(string s) {
    int n = s.length(), i = 0, cnt = 1;
    while (i < n) {
        if (!cnt) return false;
        if (s[i] == ',') i++;
        else if (s[i] == '#') cnt--, i++;
        else {
            while (i < n && s[i] != ',') i++;
            cnt++;
        }
    }
    return !cnt;
}

用图的方式思考:出度入度法

#define check(c) c <= '9' && c >= '0'

bool isValidSerialization(string s) {
    int out = 0, in = -1, n = s.size();
    for(int i = 0; i < n; i++){
        if(s[i]==',') continue;
        in++;
        if(out < in) return false;
        if(check(s[i])){
            out += 2;
            while(i < n - 1 && check(s[i + 1])) i++;
        }
    }
    return out == in;
}

序列化和反序列化二叉搜索树

拿前序遍历为例,前序拿到的元素必然是某棵子树的头,但是左右子树是多少你不知道。但 BST 的可以划分为两个区间,通过不停的收缩区间来确定值。

class Codec {
public:
    // Encodes a tree to a single string.
    string serialize(TreeNode* root) {
        string res;
        dfs_s(root, res);
        return res;
    }
    
    void dfs_s(TreeNode* root, string& res) {
        if (!root) return;
        res += to_string(root->val) + '#';
        dfs_s(root->left, res), dfs_s(root->right, res);
    }
    
    // Decodes your encoded data to tree.
    TreeNode* deserialize(string data) {
        vector<int> pre = split(data, '#');
        int u = 0;
        return dfs_d(pre, u, INT_MIN, INT_MAX);
    }

    TreeNode* dfs_d(vector<int>& pre, int& u, int l, int r) {
        if (u == pre.size() || pre[u] < l || pre[u] > r) return nullptr;
        auto root = new TreeNode(pre[u++]);
        root->left = dfs_d(pre, u, l, root->val);
        root->right = dfs_d(pre, u, root->val + 1, r);
        return root;
    }

    vector<int> split(string data, char c) {
        vector<int> res;
        int n = data.size();
        for (int i = 0; i < n; i++) {
            int num = 0;
            while (data[i] != c) {
                num *= 10;
                num += data[i] - '0';
                i++;
            }
            res.push_back(num);
        }
        return res;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值