刷题 数据结构(三)树

题目:树的层序遍历

给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。

1 代码准备

首先在 vscode 中建立二叉树,代码如下

#include<vector> 
#include<queue>
#include<iostream>
using  namespace std;

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



TreeNode* creatTree(vector<int> nums, int i) {
    if (i >= nums.size() || nums[i] == -1)
        return NULL;//判断输入是否有效
    TreeNode* root = new TreeNode(nums[i]);
    root->left = creatTree(nums, 2 * i + 1);
    root->right = creatTree(nums, 2 * i + 2);
    return root;
}


void printTree(TreeNode* root) {
    if (root == nullptr)
        return;
    queue<TreeNode*> Q;
    Q.push(root);
    int count = 0;
    while (!Q.empty()) {
        count++;
        cout << "第" << count << "层节点:";
        int size = Q.size();
        for (int i = 0; i < size; i++)
        {
            TreeNode* node = Q.front();
            Q.pop();
            cout << node->val << " ";
            if (node->left) Q.push(node->left);
            if (node->right)  Q.push(node->right);
        }
        cout << endl;
    }
}

int main()
{
    vector<int>nums = { 8,13,4,-1,-1,5,1 };
    TreeNode* root = creatTree(nums, 0);
    printTree(root);
    vector<vector<int>> result;
    result = levelOrder(root);
}

2 思考

首先应该是广度搜索就可以,然后回顾广度的思路:

  1. 放入出发点作为队列头
  2. 循环(如果列表为空则退出)
    3. 找邻居:找队列头的下一步点,将所有可能的下一步点放入队列
    4. 实际操作:本题是建立二维的数组
    5. 删除自己:当那个点的下一步点全部入队列后,删除那个点

写的时候有一些问题:

  1. 一般的广度不需要记录深度,这个好像需要,毕竟二维数组的每个小数组维度不一样
  2. 循环结束后,临时变量里面还剩点东西, 最后非要额外把剩下的放到大数组里面去,有点啰嗦
vector<vector<int>> levelOrder2(TreeNode* root) {
    vector<vector<int>> result;
    TreeNode* temp_node;
    int t = 1;
    // 1 广度优先毕然建立队列
    queue<pair<TreeNode*, int>> q;
    q.push(make_pair(root, 1)); // 问题1:需要记录深度

    vector<int> temp_int;
    //temp_int.push_back(root->val);
    while (q.empty() == false) {
        // 准备一些变量
        temp_node = q.front().first;

        // 2 获取相邻值
        if (temp_node->left != nullptr)
        {
            q.push(make_pair(temp_node->left, q.front().second + 1));
        }
        if (temp_node->right != nullptr)
        {
            q.push(make_pair(temp_node->right, q.front().second + 1));
        }

        if (t != q.front().second) // 表明下一层了,执行:把现有的vector结束了,建立新的
        {
            result.push_back(temp_int);
            temp_int.clear();
            t = q.front().second;
        }
        temp_int.push_back(temp_node->val);

        // 3 队列弹出
        q.pop();
    }
    if (temp_int.size() != 0) // 问题2:非要最后来一下
    {
        result.push_back(temp_int);
    }
    return result;
}

然而别人的参考代码十分简单,这两个都没有出现过

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

    queue <TreeNode*> q;
    q.push(root);
    while (!q.empty()) {
        int currentLevelSize = q.size(); // ?
        ret.push_back(vector <int>());
        for (int i = 1; i <= currentLevelSize; ++i) {
            auto node = q.front(); q.pop();
            ret.back().push_back(node->val);
            if (node->left) q.push(node->left);
            if (node->right) q.push(node->right);
        }
    }

    return ret;
}

分析:

  1. [巧妙利用队列长度,不记录深度] 广度算法中,当一层结束的时候,队列的长度=下一层节点数量,这样就不需要记录深度了。
  2. [不用临时变量] 由于我的算法是发现节点切换到下一层的时候,才把临时变量放到总变量中,但最后一层的时候,不会切换到下一层了,所以得额外来一下。直接放到总变量的最后一个数组即可 ret.back().push_back(node->val);

也可以使用深度遍历。其实这种题目就是:遍历方法(广度或深度)+ 实际操作(放到二维数组中去)

class Solution {
public:
    vector<vector<int>> ret;
    void level(TreeNode* root, int lev) {
        if(!root) return;
        if (lev >= ret.size()) {
            ret.push_back(vector<int>());
        }
        ret[lev].push_back(root -> val); // 放到二维数组中去
        level(root -> left, lev + 1);
        level(root -> right, lev + 1);
    }
    vector<vector<int>> levelOrder(TreeNode* root) {
        level(root, 0);
        return ret;
    }
};

完整代码记录

 
#include<vector> 
#include<queue>
#include<iostream>
using  namespace std;

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



TreeNode* creatTree(vector<int> nums, int i) {
    if (i >= nums.size() || nums[i] == -1)
        return NULL;//判断输入是否有效

    TreeNode* root = new TreeNode(nums[i]);
    root->left = creatTree(nums, 2 * i + 1);
    root->right = creatTree(nums, 2 * i + 2);

    return root;
}


void printTree(TreeNode* root) {
    if (root == nullptr)
        return;

    queue<TreeNode*> Q;
    Q.push(root);
    int count = 0;
    while (!Q.empty()) {
        count++;
        cout << "第" << count << "层节点:";
        int size = Q.size();
        for (int i = 0; i < size; i++)
        {
            TreeNode* node = Q.front();
            Q.pop();
            cout << node->val << " ";
            if (node->left) Q.push(node->left);
            if (node->right)  Q.push(node->right);
        }
        cout << endl;
    }
}

 
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> result;
        TreeNode* temp_node;
        int t = 0;
        // 1 广度优先毕然建立队列
        queue<pair<TreeNode*, int>> q;
        q.push(make_pair(root,1));

        vector<int> temp_int;
        temp_int.push_back(root->val);
        while (q.empty() == false) {
            // 准备一些变量
            temp_node  = q.front().first;
            if (t != q.front().second) // 表明下一层了,执行:把现有的vector结束了,建立新的
            {
                result.push_back(temp_int);
                temp_int.clear();
                t = q.front().second;
            }
            
            // 2 获取相邻值
            if (temp_node->left != nullptr)
            {
                q.push(make_pair(temp_node->left,t+1));
            }
            if (temp_node->right != nullptr)
            {
                q.push(make_pair(temp_node->right, t + 1));
            }
        }

    }
};

vector<vector<int>> levelOrder2(TreeNode* root) {
    vector<vector<int>> result;
    TreeNode* temp_node;
    int t = 1;
    // 1 广度优先毕然建立队列
    queue<pair<TreeNode*, int>> q;
    q.push(make_pair(root, 1));

    vector<int> temp_int;
    //temp_int.push_back(root->val);
    while (q.empty() == false) {
        // 准备一些变量
        temp_node = q.front().first;

        // 2 获取相邻值
        if (temp_node->left != nullptr)
        {
            q.push(make_pair(temp_node->left, q.front().second + 1));
            

        }
        if (temp_node->right != nullptr)
        {
            q.push(make_pair(temp_node->right, q.front().second + 1));
        }

        if (t != q.front().second) // 表明下一层了,执行:把现有的vector结束了,建立新的
        {
            result.push_back(temp_int);
            temp_int.clear();
            t = q.front().second;
        }
        temp_int.push_back(temp_node->val); 

        // 3 队列弹出
        q.pop();
    }
    if (temp_int.size() != 0)
    {
        result.push_back(temp_int);
    }
    return result;
}
// 1. ? 广度是否需要记录深度?
vector<vector<int>> levelOrder(TreeNode* root) {
    vector <vector <int>> ret;
    if (!root) {
        return ret;
    }

    queue <TreeNode*> q;
    q.push(root);
    while (!q.empty()) {
        int currentLevelSize = q.size(); // ?
        ret.push_back(vector <int>());
        for (int i = 1; i <= currentLevelSize; ++i) {
            auto node = q.front(); q.pop();
            ret.back().push_back(node->val);
            if (node->left) q.push(node->left);
            if (node->right) q.push(node->right);
        }
    }

    return ret;
}
 
int main()
{
    vector<int>nums = { 8,13,4,-1,-1,5,1 };
    TreeNode* root = creatTree(nums, 0);
    printTree(root);
    vector<vector<int>> result;
    result = levelOrder(root);

}

二. 题目:

  1. 翻转二叉树

在学习了对称二叉树后,感觉这类题目用嵌套十分简答

    TreeNode* invertTree(TreeNode* root) {
        TreeNode* temp = 0;
        if (root == nullptr) return root;
        temp = root->right;
        root->right = root->left;
        root->left = temp;
        invertTree(root->left);
        invertTree(root->right);
        return root;
    }

思考

一开始我们学的前序、中序有什么用呢?为什么一直没有用到?其实用到了,只是我们不知道。比如上述代码中,你是先嵌套左还是右边,是先处理根节点的交换还是先嵌套?这几个顺序其实无所谓,但每种都对应了一种遍历方法。上述就是中序遍历。


三. 题目

  1. 路径总和

典型的深度遍历算法就可以

    bool lujin(TreeNode* root, int targetSum, int current)
    {
        if ( root->left == nullptr && root->right == nullptr) 
        {
            if (targetSum == current) return true;  // 可以改进
            else return false;
        }
        bool t = 0;
        if (root->left != nullptr)
            t = lujin(root->left, targetSum, current + root->left->val);
        if (t) return true;
        if (root->right != nullptr) // 可以改进
            t = lujin(root->right, targetSum, current + root->right->val);
        if (t) return true;
        return false;

    }

    bool hasPathSum(TreeNode* root, int targetSum) {
        if (root == nullptr) return false;

        int sum_ = root->val;
        return lujin(root, targetSum, sum_);

    }

思考

逻辑没有问题,但是写的太乱了!
注意几个简便的写法

if (targetSum == current) return true; else return false;// 改为 return (targetSum == current)
hasPathSum(root->left, sum - root->val) ||
               hasPathSum(root->right, sum - root->val);

四、题目

二叉搜索树的搜索

其实我也知道嵌套的

    TreeNode* searchBST(TreeNode* root, int val) {
    TreeNode* temp = 0;
    if (root == nullptr) return root;
    if (root->val == val) return root;
    if (val < root->val) 
    {
        temp = searchBST(root->left, val); //? 有无简便写法?
    }
    else
    {
        temp = searchBST(root->right, val);
    }
    if (temp != nullptr) return temp;
    return nullptr;
    }

但是写的不美观,看看别人的代码


class Solution {
public:
    TreeNode* searchBST(TreeNode* root, int val) 
    {
        if(root==nullptr) return nullptr; // 注意 1  这里不是返回root,是nullptr
        if(root->val==val) return root;
        if(root->val>val)  return searchBST(root->left,val);
        if(root->val<val) return searchBST(root->right,val); // 简便的写法
        //讲道理走不到这里
        return nullptr;
        
    }
};

五、题目

  1. 验证二叉搜索树

本来以为只要 左<中<右 ,但是发现还得满足父的要求;也就是说,下面的子节点是有一个取值范围的。
先来看看我的



bool isValidBST2(TreeNode* root, long long up, long long low) {
    if (root == nullptr) return true; //? 返回什么,就是这个,因为不影响“和”的判断
    // 两个叶节点需要在范围内;左边 中 右边;嵌套函数并更新上下界限
    bool r1=1,r2=1;
    if (root->left)
    {
        if ((root->left->val >= root->val) || (root->left->val >= up) || (root->left->val <= low))
            return false;

        r1 = isValidBST2(root->left, root->val, low);
    }

    if (root->right)
    {
        if ((root->right->val <= root->val) || (root->right->val >= up) || (root->right->val <= low))
            return false;
        r2 = isValidBST2(root->right, up, root->val);
    }

    return r1 && r2;
    }

bool isValidBST(TreeNode* root) {
        if (root == nullptr) return true; //? 返回什么,就是这个,因为不影响“和”的判断

        return isValidBST2(root, LONG_MAX, LONG_MIN); //  
    }

注意

  1. 一开始是 负无穷 - 正无穷,c++没有这个,所以用 LONG_MAX, LONG_MIN 这两个常量来写,注意类型不是 int ,而是 long long .
  2. 好像不需要什么 “两个叶节点需要在范围内;左边 中 右边;嵌套函数并更新上下界限”,只要比较当前节点的值是否在范围内部就好。
  3. 在嵌套的时候注重于当前节点就好,不需要判断是否有左右节点(如下面的代码)
  4. 大致就几步
    1. [结束调节] 省去是否有左右节点的判断
    2. [实际操作] 判断 当前节点是否超出范围
    3. [嵌套左右]

所以看别人的代码

bool isValidBST(TreeNode *root, long left = LONG_MIN, long right = LONG_MAX) {
     if (root == nullptr)
         return true;
     long x = root->val;
     return left < x && x < right &&
            isValidBST(root->left, left, x) &&
            isValidBST(root->right, x, right);
 }

作者:endlesscheng
链接:https://leetcode.cn/problems/validate-binary-search-tree/solution/qian-xu-zhong-xu-hou-xu-san-chong-fang-f-yxvh/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
};

思考:各种遍历的关系、用法和区别

看到这篇解答给出的分别用前序中序后序写的代码,发现这三种区别挺大的(也就是说不一定有:维护一个取值范围 这种操作,而且只有前序遍历需要一个动态的范围)。

首先,这三种有什么区别?

  1. 首先,前中后都是对于父来说的,永远都是先左后右;

  2. 前序遍历的思想(也是我想到的。虽然不是刻意的,但是我看到这题写出来的代码后,发现就是前序遍历)。看到题目,大概是递归,但是关键在于递归中的实际操作,自然的我们会把树画出来,然后找到某个节点,比如“24”,发现他首先要比21大,还要比28小(其实第二个条件我一开始没想出来)。所以就知道节点的值是在一个范围内的。因为树都是从根节点开始的吗,所以就先看看根节点(这个想发就是前序遍历)。根节点是没有限制的,但是越往下就限制越大,所以递归的时候:1判断值是否在范围内部;2 更新这个范围给子树。

  3. 中序遍历的代码如下:

    1. 嵌套左
    2. 判断自己是否小于一个值
    3. 更新这个值
    4. 嵌套右

    但是你会问,你怎么想到这样做的?大概就是先一路向左找到全局最小,左、中、右依次更新这个值(没有就跳过),然后用这个值去给中节点的父节点设置下限。

bool isValidBST(TreeNode* root) {
    if (root == nullptr)
        return true;
    if (!isValidBST(root->left))
    {
        return false;
    }
    if (root->val <= pre)
    {
        return false;
    }
    pre = root->val;
    return isValidBST(root->right);
}
  1. 后续遍历的代码有点逆天,不知道怎么才能想出来。就不分析了。

六、题目

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

我实在是不清楚如何做,只能把2点的父节点全部打印出来,然后找最小的那个。找所有父节点的方法是
[实际操作] 包括:判断相等 + 放入堆栈 + 弹出堆栈,但是具体作的时候这些步骤可能和嵌套代码穿插着来,也就是顺序要对!比如下面的代码顺序

  1. [实际操作1] 无论怎样,先把自己放到自己的父节点列表中(一般来说父节点是不包括自己的,但是这里严格说是找到某节点的路径列表)
  2. [实际操作2] 看看当前是不是要找的值,是就返回True(表明已经把目标节点放入路径中),不是就继续;
  3. [嵌套1] 看看基于当前的路径列表,往左走试试。如果函数判断为真(表明已经把目标节点放入路径中),就可以返回了(就是说当前函数的任务已完成);
  4. [嵌套2] 看看基于当前的路径列表,往右边走试试。如果函数判断为真(表明已经把目标节点放入路径中),就可以返回了(就是说当前函数的任务已完成);
  5. [实际操作3] 都不行的时候只能掉头了(弹出当前节点); 这个操作的位置要注意,要在两个嵌套之后
  6. [最后返回] 没办法告诉前面的,我这个函数没找到目标节点。
bool lowest(TreeNode* root, TreeNode* p,  vector<TreeNode*> & vec)
{
    if (root == nullptr) return false; // 这个返回什么?应该是对的,就是告诉上面的,我没找到呀!
    vec.push_back(root);
    
    if (root->val == p->val) return true;
    if (lowest(root->left, p, vec)) return true;
    if (lowest(root->right, p, vec)) return true;

    vec.pop_back();
    return false;

}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
    if (p->val > q -> val)
    {
        swap(p, q);
    }
    vector<TreeNode*> vec;
    vector<TreeNode*> vec2;
    //vec.push_back(root);
    TreeNode* result;
    lowest(root, p, vec);
    lowest(root, q, vec2); // 某个节点所有父节点
    int i = 0;
    for (; i < min(vec.size(), vec2.size()); i++)
    {
        if (vec[i]->val != vec2[i]->val)
        {
            return vec[i - 1];
        }
    }
    return vec[i-1 ];
}

所以上面的代码可以命名为:找出到目标节点的路径 算法。
但是我没有利用“搜索二叉树”的前提,如果用的话,找路径可能定会简单许多(见下面官方代码一)。更强的还可以利用“分岔点”思想(代码二)

// 官方代码一
class Solution {
public:
    vector<TreeNode*> getPath(TreeNode* root, TreeNode* target) {
        vector<TreeNode*> path;
        TreeNode* node = root;
        while (node != target) {
            path.push_back(node);
            if (target->val < node->val) {
                node = node->left;
            }
            else {
                node = node->right;
            }
        }
        path.push_back(node);
        return path;
    }

    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        vector<TreeNode*> path_p = getPath(root, p);
        vector<TreeNode*> path_q = getPath(root, q);
        TreeNode* ancestor;
        for (int i = 0; i < path_p.size() && i < path_q.size(); ++i) {
            if (path_p[i] == path_q[i]) {
                ancestor = path_p[i];
            }
            else {
                break;
            }
        }
        return ancestor;
    }
};

作者:LeetCode-Solution
链接:https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-search-tree/solution/er-cha-sou-suo-shu-de-zui-jin-gong-gong-zu-xian-26/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


// “分叉点”官方代码二
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
    TreeNode* ancestor = root;
    while (true) {
        if (p->val < ancestor->val && q->val < ancestor->val) {
            ancestor = ancestor->left;
        }
        else if (p->val > ancestor->val && q->val > ancestor->val) {
            ancestor = ancestor->right;
        }
        else {
            break;
        }
    }
    return ancestor;
}

作者:LeetCode-Solution
链接:https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-search-tree/solution/er-cha-sou-suo-shu-de-zui-jin-gong-gong-zu-xian-26/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值