C++刷题笔记:树


求树的深度

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;
};

全部评论

相关推荐

点赞 收藏 评论
分享
牛客网
牛客企业服务