C++刷题笔记:树


前序/中序/后序遍历

求后序遍历

参考 根据前序遍历和中序遍历求后序遍历

已知前序遍历abdgcefh,中序遍历dgbaechf

在这里插入图片描述
根据前序确定根节点,根据中序确定子序列。再在子序列中确定父节点,以此类推。

那么,后序遍历为:gdbehfca

递归法

void inorder_traversal_dfs(TreeNode *root, vector<int> &ret) {
    if (root == nullptr) return;
    inorder_traversal_dfs(root->left, ret);
    ret.push_back(root->val);
    inorder_traversal_dfs(root->right, ret);
}

vector<int> inorder_traversal(TreeNode *root) {
    vector<int> ret;
    inorder_traversal_dfs(root, ret);
    return ret;
}

迭代法

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

迭代法层序遍历

广度优先搜索算法 BFS:手动维护一个队列,先把根节点入队,每次出队之前,把非空的左右子结点入队。题解自 https://leetcode-cn.com/problems/binary-tree-level-order-traversal/
在这里插入图片描述

vector<vector<int>> level_order(TreeNode *root) {
    vector<vector<int>> results;
    if (!root) 	return results;
    
    queue<TreeNode *> q;
    q.push(root);
    while (!q.empty()) {
        int n = q.size();
        vector<int> level(n);
        for (int i = 0; i < n; i++) {
            level[i] = q.front()->val;
            if (q.front()->left) q.push(q.front()->left);
            if (q.front()->right) q.push(q.front()->right);
            q.pop();
        }
        results.push_back(level);
    }
    return results;
}

求树的深度

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

class Solution {
public:
    int TreeDepth(TreeNode* pRoot)
    {
         if(pRoot == NULL) return 0;
         return max(TreeDepth(pRoot->left), TreeDepth(pRoot->right)) + 1;
    }
};

判断平衡二叉树

输入一棵二叉树,判断该二叉树是否是平衡二叉树。在这里,我们只需要考虑其平衡性,不需要考虑其是不是排序二叉树。

class Solution {
public:
    int depth(TreeNode *root) {
        if (!root) return 0;		// 空树也是平衡树
        
        int l = depth(root->left);   // 提前截止
        if (l == -1) return -1;
        
        int r = depth(root->right);  // 提前截止
        if (r == -1) return -1;
        
        if (abs(l - r)> 1) return -1;
        return max(l, r) + 1;
    }
    bool IsBalanced_Solution(TreeNode* root) {
        return depth(root) != -1;
    }
};

重建二叉树

输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

思路:

  • 递归截止:子树序列中无结点
  • 由前序序列确定根结点
  • 在中序序列中根据root的索引,分为左右子树
  • 分别递归左子树和右子树
  • 返回根节点
    在这里插入图片描述
class Solution {
public:
    TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
        // 递归求解
        return reconstruct(pre, 0, pre.size(), vin, 0, vin.size());
    }
    
    TreeNode* reconstruct(vector<int>& pre, int pl, int pr, 
                          vector<int>& vin, int vl, int vr){
        // 前序没有结点时,迭代终止
        if(pl==pr) return nullptr;
        // 根节点
        TreeNode* root = new TreeNode(pre[pl]);
        // 确定中序遍历中根节点的索引,分为左右子树,进行递归
        for(int i=vl; i<vr; ++i){
            if(vin[i] == root->val){
                int num = i - vl; // 左子树的结点个数
                root->left = reconstruct(pre, pl+1, pl+1+num, vin, vl, i);
                root->right = reconstruct(pre, pl+1+num, pr, vin, i+1, vr);
                break;
            }
        }
        return root;
    }
};

滑动窗口最大值

题目描述:
给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。

分析:
可以用暴力遍历的方法,时间复杂度:O(n*k), 其中n为数组大小,k为窗口大小。也可以用平衡二叉树或完全二叉树(大顶堆),这里只做试验:

平衡二叉树 map 自动排序,从小到大:将每个窗口的数据存入,删除老数据,存入新数据,读出最大的即可。

vector<int> maxInWindows(const vector<int>& num, unsigned int size) {
    if (num.size() == 0 || size <= 0 || num.size() < size) return {};
    vector<int> ret;
    map<int, int> win;  // 元素:索引
    for (int i = 0; i < num.size(); ++i) {
        if (win.size() < size) {
            win[num[i]] = i;
            if (size == win.size()) {
                ret.push_back(win.rbegin()->first);
            }
        }
        else {
            win.erase(num[i - size]);
            win[num[i]] = i;
            ret.push_back(win.rbegin()->first);
        }
    }
    return ret;
}

数据流的中位数

题目描述:
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。

分析:
(1) 最简单的:每次插入vector,取中位数时排序 std::sort( v.begin(), v.end() );
(2) 插入的同时,进行排序:low_bound

class Solution2 {
public:
    void Insert(int num)
    {
        if (arr.empty()) arr.push_back(num);
        else {
            auto it = lower_bound(arr.begin(), arr.end(), num); // lower/upper都适用
            arr.insert(it, num);
        }
    }

    double GetMedian()
    {
        int n = arr.size();
        return n & 1 ? arr[n / 2] : (arr[n / 2 - 1] + arr[n / 2]) * 0.5;
    }

private:
    vector<int> arr;
};

注意:(n>>2) - 1 移位运算符的优先级低于算术运算符!!!判断奇偶可以用位运算符

(3) 利用优先队列:大顶堆+小顶堆

// 大顶堆,用来存放中位数的较小数 lows
// 小顶堆,用来存放中位数的较大数 ups

  • 最开始都是空堆,属于平衡状态
  • lows中存进一个数,就要转给ups一个
  • 如果 lows.size() < ups.size() 说明转多了,要补回来
class Solution3 {
public:
    void Insert(int num)
    {
        lows.emplace(num);
        ups.emplace(lows.top());
        lows.pop();

        if (lows.size() < ups.size()) {
            lows.emplace(ups.top());
            ups.pop();
        }
    }

    double GetMedian()
    {
        return lows.size() > ups.size() ? lows.top() : (lows.top() + ups.top()) * 0.5;
    }

private:
    priority_queue<int> lows;// 大顶堆,用来存放中位数的较小数
    priority_queue<int, vector<int>, greater<int>> ups;  // 小顶堆,用来存放中位数的较大数
};

寻找第K个结点

题目描述:
给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。

分析:
这题比较难。二叉搜索树的中序遍历就是将结点从小到大排序,所以第1小的结点就是遍历左子树,一直到空。想到的就是递归,遍历到叶子结点后,第i次回溯就是第i小的结点,这时要考虑右子树。

增序: 1 2 3 4
降序: 4 3 2 1
所以找第k小,就是找 k-- == 1

class Solution {
public:
    TreeNode* KthNode(TreeNode* pRoot, int k)
    {
        this->k = k;
        dfs(pRoot);
        return node;
    }
    
    void dfs(TreeNode* p){
        // 节点不存在或已经找到则返回
        if(!p || k<1) return;
        
        // 继续寻找左子树
        dfs(p->left);
        
        // 回溯
        if(k==1) node = p;
        k--;
        if(p->right)  dfs(p->right);  
    }
    
private:
    TreeNode* node = nullptr;
    int k = 1;
};

判断二叉树是否镜像

class Solution {
public:
    bool isSymmetrical(TreeNode* pRoot){
        return isSame(pRoot, pRoot);
    }
    
    bool isSame(TreeNode* r1, TreeNode* r2){
        if(!r1 && !r2) return true;			// 结点同时不存在
        if(!r1 || !r2) return false;		// 结点只存在一个
        return r1->val == r2->val 			// 结点同时都存在
            && isSame(r1->left, r2->right) 
            && isSame(r2->left, r1->right);
    }

};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值