分治算法和树

一:分治算法

「 divide and conquer」,全称分而治之,是一种非常重要且常见的算法策略。

分治通常基于递归实现,包括“分”和“治”两个步骤。

  • 分(划分阶段):递归地将原问题分解为两个或多个子问题,直至到达最小子问题时终止。
  • 治(合并阶段):从已知解的最小子问题开始,从底至顶地将子问题的解进行合并,构建出原问题的解。

如何判断分治问题

一个问题是否可以使用分治解决的几个判断依据:

  1. 问题可以被分解

    原问题可以被分解成规模更小、类似的子问题,以及能够以相同方式递归地进行划分。

  2. 子问题是独立的

    子问题之间是没有重叠的,互相没有依赖,可以被独立解决。

  3. 子问题的解可以被合并

    原问题的解通过合并子问题的解得来。

分治法的过程——一分二治三合并

:递归或迭代地将原问题分解为各个的子问题(性质相同的、相互独立的子问题)
:若子问题规模足够小则直接解决,否则递归解决各子问题
合并:逐层合并各子问题的解得到原问题的解

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

关键点:

原问题和各个子问题都是同类的重复子问题—满足递归的定义

除了向下递归问题还要向上合并结果

通过分治可以提高效率

分治不仅可以有效地解决算法问题,往往还可以带来算法效率的提升

在排序算法中,快速排序、归并排序、堆排序相较于选择、冒泡、插入排序更快,就是因为它们应用了分治策略。

归并排序

在这里插入图片描述

void mergeSort(int a[],int l,int r){
     /*递归结束条件*/
     if(l==r)  return;//当前只有一个元素
     int mid = (l + r) / 2;
     /*分治*/
     //[l,..,r]分成[l,..,mid];[mid+1,...,r]
     mergeSort(a, 0, mid);
     mergeSort(a, mid + 1, r);
     /*合并结果*/
     //[l,..,mid]和[mid+1,r]进行排序
     //用辅助数组aux存储排序结果,然后赋值给原数组a
     int *aux = new int[r - l + 1];
     int i = l, j = mid + 1;
     int k = 0;//aux指向
     while(i<=mid && j<=r){
        if(a[i]<=a[j]){
            aux[k++] = a[i++];
        }else{
            aux[k++] = a[j++];
        }
     }
     //分析谁在范围内
     while(i<=mid){
         aux[k++] = a[i++];
     }
     while(j<=r){
         aux[k++] = a[j++];
     }
     //把aux[0,..,r-l+1]这段范围赋值给a[l,...r]
     for (int t = l; t <=r ;t++){
         a[t] = aux[t - l];
     }
}

分治三大步骤:

  • 递归结束条件
  • 进行分治,自顶向下
  • 合并结果,自底向上

也可以把合并结果单独写成函数

void _mergeHelper(int a[], int l, int mid, int r){
    int *aux = new int[r - l + 1];
    // 对[l,..,mid]和[mid+1,..,r]进行排序
   int i = l, j = mid + 1;
    for(int k=0;k<=r-l+1;k++){//r-l+1取等
        if(i>mid){
            aux[k]=a[j++];
        }
        else if(j>r){
            aux[k]=a[i++];
        }
        else if(a[i]>a[j]){
            aux[k]=a[j++];
        }
        else{
            aux[k]=a[i++];
        }
    }
    //赋值
    for (int t = l; t <= r; t++){
        // a中[l,r]对应aux是[0,r-l+1]
        a[t] = aux[t - l];
    }
}
void mergeSort(int a[], int l, int r)
{
    /*递归结束条件*/
    if (l == r)
        return; // 当前只有一个元素
    int mid = (l + r) / 2;
    /*分治*/
    //[l,..,r]分成[l,..,mid];[mid+1,...,r]
    mergeSort(a, 0, mid);
    mergeSort(a, mid + 1, r);
    /*合并结果*/
    _mergeHelper(a, l, mid, r);
}

对应for中,也就是:

条件1:i<=mid

条件2: j<=r

  • 条件一不满足
  • 条件二不满足
  • 条件一二都满足,比较大小
for (int k = 0; k <= r - l + 1; k++){ // r-l+1取等
        if (i <= mid && j <= r){
            //比较必须都在范围
            if (a[i] > a[j]){
                aux[k] = a[j++];
            }
            else{
                aux[k] = a[i++];
            }
        }
        //一端越界,只复制另一端
        else if (i>mid){
            aux[k] = a[j++];
        }
        else {//j>r
            aux[k] = a[i++];
        }
    }

力扣22

22. 括号生成

分治做法

思路:(a)b
( a ) b , n u m s ( a ) 表示 a 生成的括号个数 \left( a \right) b\text{,}nums\left( a \right) \text{表示}a\text{生成的括号个数} (a)bnums(a)表示a生成的括号个数

n = 2 n=2 n=2

n u m s ( a ) = 1 , n u m s ( b ) = 0  结果: ( ( ) ) nums\left( a \right) =1,nums\left( b \right) =0\ \text{结果:}\left( \left( \right) \right) nums(a)=1,nums(b)=0 结果:(())

n u m s ( a ) = 0 , n u m s ( b ) = 1  结果: ( ) ( ) nums\left( a \right) =0,nums\left( b \right) =1\ \text{结果:}\left( \right) \left( \right) nums(a)=0,nums(b)=1 结果:()()

n = 3 n=3 n=3

n u m s ( a ) = 2 , n u m s ( b ) = 0  结果: ( ( ) ( ) )  或 ( ( ( ) ) ) nums\left( a \right) =2,nums\left( b \right) =0\ \text{结果:}\left( \left( \right) \left( \right) \right) \ \text{或}\left( \left( \left( \right) \right) \right) nums(a)=2,nums(b)=0 结果:(()()) ((()))

n u m s ( a ) = 1 , n u m s ( b ) = 1    结果: ( ( ) ) ( ) nums\left( a \right) =1,nums\left( b \right) =1\,\,\text{结果:}\left( \left( \right) \right) \left( \right) nums(a)=1,nums(b)=1结果:(())()

n u m s ( a ) = 0 , n u m s ( b ) = 2    结果: ( ) ( ) ( ) 或 ( ) ( ( ) ) nums\left( a \right) =0,nums\left( b \right) =2\,\,\text{结果:}\left( \right) \left( \right) \left( \right) \text{或}\left( \right) \left( \left( \right) \right) nums(a)=0,nums(b)=2结果:()()()()(())

 vector<string> generateParenthesis(int n) {
        vector<string> res;
        //递归结束,底部
        if(n==0) return {""};
        //(a)b
        for(int i=1;i<=n;i++){
            //分治
            vector<string> aa=generateParenthesis(i-1);
            vector<string> bb=generateParenthesis(n-i);
            //向上合并aa与bb的结果
            //乘法原理,每一个aa和每一个bb组合
            for(auto m:aa){//m是aa的一个
                for(auto n:bb){//n是bb的一个
                    res.push_back("("+m+")"+n);
                }
            }
        }
        return res;
    }

回溯做法

先回溯后选元素,所以不符合条件不进行回溯

class Solution {
public:
    vector<string> generateParenthesis(int n) {
        string str;
        this->n=n;
        findpar(str, 0, 0);
        return res;
    }

private:
    vector<string> res;
    int n;
    void findpar(string str, int l, int r) {
        /*右括号数量必须少于等于左括号*/
        if (l == n && r == n) {
            res.push_back(str);
            return;
        }
        str += "(";
        //生成左括号条件
        if (l < n) {
            findpar(str, l + 1, r);
        }
        str.pop_back();//去掉该选择
        str += ")";
        //生成右括号条件
        if (r < l) {
            findpar(str, l, r + 1);
        }
    }
};

或者选用声明两个变量,无需pop_back()

 void findpar(string str, int l, int r) {
        /*右括号数量必须少于等于左括号*/
        if (l == n && r == n) {
            res.push_back(str);
            return;
        }
        string s1=str+"(";
        string s2=str+")";
        //生成左括号条件
        if (l < n) {
            findpar(s1, l + 1, r);
        }
        //生成右括号条件
        if (r < l) {
            findpar(s2, l, r + 1);
        }
    }
 // 生成左括号条件
        if (l < n) {
            findpar(str + "(", l + 1, r);
        }
   // 生成右括号条件
        if (r < l) {
            findpar(str + ")", l, r + 1);
        }

也可以采用先选元素再回溯的策略,不过要加候选项

class Solution {
public:
    vector<string> generateParenthesis(int n) {
        this->n = n;
        string str;
        findPar(str, 0, 0);
        return res;
    }

private:
    int n;
    string data = "()"; // 供选
    vector<string> res;
    void findPar(string str, int l, int r) {
        /*不满足条件结束---剪枝*/
        if (l > n || r > l) {
            return;
        }
        /*满足条件记录结果*/
        if (l == n && r == n) {
            res.push_back(str);
            return;
        }
        for (int i = 0; i < data.size(); i++) {
            /*加元素统计l,r*/
            if (data[i] == '(')
                l++;
            else
                r++;
            /*先加再回溯*/
            str += data[i];
            findPar(str, l, r);
            str.pop_back();
            /*去元素统计l,r*/
            if (data[i] == '(')
                l--;
            else
                r--;
        }
    }
};

for (int i = 0; i < data.size(); i++):中:data="()"就是候选项

注意不符合条件的要剪枝,就是提前return;

力扣169

169. 多数元素

类似归并排序,首先每次对半分数组,直至只剩自己

但是在合并结果时候需要考虑:这里是求出现最多的

需要记录左边出现最多的和右边出现最多的,返回出现最多的那个值即可

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        return findMajor(nums,0,nums.size()-1);
    }
private:
    /*利用分治,分别求左右两个区间的众数*/
    int findMajor(vector<int>& nums,int l,int r){
        if(l==r) return nums[l];//递归结束
        int mid=(l+r)/2;
        /*分治*/
        int left=findMajor(nums,l,mid);
        int right=findMajor(nums,mid+1,r);
        /*合并结果*/
        //记录频次
        if(left==right) return left;
        int col1=0,col2=0;
        for(int i=l;i<=r;i++){
           if(nums[i]==left) col1++;
           if(nums[i]==right) col2++;
        }
        return (col1>col2)?left:right;
    }
};

也可以利用map统计频次,针对这道题更简单

   int majorityElement(vector<int>& nums) {
        unordered_map<int,int> mp;
        for(auto &val:nums){
            if(mp.find(val)!=mp.end()){
                  mp[val]++;
            }else{
                  mp[val]=1;
            }
            //输出
            if(mp[val]>nums.size()/2){
                return val;
            }
        }
        return -1;
    }

二:树

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

父节点,兄弟节点,子树

满二叉树:

  • 一个二叉树所有的非叶子节点都存在左右两个孩子

  • 并且所有叶子节点都在同一层级上

    这样的树被称为满二叉树

完全二叉树

  • 从根往下数,除了最下层外都是全满(都有两个子节点)

  • 而且最下层所有叶结点都向左边靠拢填满。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这样定义是为了从根依次向下,按照从左至右的顺序标号后可以快速计算出给定节点的孩子和双亲节点

complete-binary-tree-num

根节点编号为0:

  • 对于i>0,其父节点的编号为*(i-1)/2*
  • 2·i+1<n,其左子节点的序号为2·i+1,否则没有左子节点
  • 2·i+2<n,其右子节点的序号为2·i+2,否则没有右子节点

完全二叉树可以用数组存储,编号就可以求出父节点和子节点的,可以表明树关系

树的遍历

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

dfs和bfs

用递归方法求层次遍历

  • 选用先序遍历,先访问节点,在依次访问左子树和右子树
  • 关键在于输出,需要维护一个当前节点所在层level的信息
  • 用for遍历每一层节点,当level在该层后,就进行输出

首先创建好二叉树

struct TreeNode
{
  int val;
  TreeNode *left, *right;
  TreeNode(int x, TreeNode *left = NULL, TreeNode *right = NULL) : val(x), left(left),\           right(right) {}
};
TreeNode* createTree(){
  /*    30
      20  40
   70 80  50 100
 */
  TreeNode *a = new TreeNode(70);
  TreeNode *b = new TreeNode(80);
  TreeNode *c = new TreeNode(50);
  TreeNode *d = new TreeNode(100);
  TreeNode *e = new TreeNode(20, a, b);
  TreeNode *f = new TreeNode(40, c, d);
  TreeNode *g = new TreeNode(30, e, f);
  return g;
}

求二叉树深度函数

int depth(TreeNode* root){
    if(root==NULL)
      return 0;
    return max(depth(root->left), depth(root->right)) + 1;
}

递归层次遍历–不分层

void _dfs(TreeNode* root,int level){
      if(!root)//左右孩子不存在情况
       return;
      if(level==1){//只输出该层节点
        cout << root->val << " ";
        return;
      }
      _dfs(root->left, level - 1);
      _dfs(root->right, level - 1);
}
void levelTraverse(TreeNode* root){
    if(root==NULL)
      return;
    int dep = depth(root);//求出树深度
    //对每一层做好标记,从根节点1开始
    for (int i = 1; i <= dep;i++){
      _dfs(root, i);
      // cout<<endl;//分层
    }
}

同理N叉数的层次遍历

429. N 叉树的层序遍历

递归:

class Solution {
public:
    vector<vector<int>> levelOrder(Node* root) {
        dfs(root,0);
        return res;
    }
private:
    vector<vector<int>> res;
    void dfs(Node* root,int level){
        if(root==NULL) return;
        if(level==res.size()){
            res.push_back({});
        }
        //存结点
        res[level].push_back(root->val);
        //递归
        for(auto& x:root->children){
            dfs(x,level+1);
        }
    }
};

非递归:

 vector<vector<int>> levelOrder(Node* root) {
        vector<vector<int>> res;
        if(root==NULL) return res;
        using pr=pair<Node*,int>;
        queue<pr> que;
        que.push(make_pair(root,0));
        while(!que.empty()){
            auto [tmp,level]=que.front();
            que.pop();
            if(level==res.size()){
                res.push_back({});
            }
            res[level].push_back(tmp->val);
            for(auto& x:tmp->children){
                que.push(make_pair(x,level+1));
            }
        }
        return res;
    }

二叉树中序

94. 二叉树的中序遍历

细节:一定要把结果作为全局变量,或者引用参数传递

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        if(root==NULL) return {};
        //左根右
        inorderTraversal(root->left);
        res.push_back(root->val);
        inorderTraversal(root->right);
        return res;
    }
private:
    vector<int> res;//结果
};
class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> res;
        dfs(root,res);
        return res;
    }
private:
    void dfs(TreeNode *root,vector<int>& ans){//必须传引用
         if(root==NULL) return;
         dfs(root->left,ans);
         ans.push_back(root->val);
         dfs(root->right,ans);
    }
};

或者结果作为私用变量,另写一个void类型的dfs

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        dfs(root);
        return ans;
    }
private:
    vector<int> ans;
    void dfs(TreeNode *root){//必须传引用
         if(root==NULL) return;
         dfs(root->left);
         ans.push_back(root->val);
         dfs(root->right);
    }
};

二叉树层次遍历,区分出层次

102. 二叉树的层序遍历

关键在于:如何分层

循环前,当前队列里的元素均在同一层

然后依次访问节点,自己出队,把其左右孩子放入队列

用变量记录队列元素作为循环结束标志

vector<vector<int>> levelOrder(TreeNode* root) {
        if(root==NULL) return {};
        vector<vector<int>> res;
        //定义队列
        queue<TreeNode*> que;
        //根节点入队
        que.push(root);
        //遍历
        while(!que.empty()){
            int cursize=que.size();
            vector<int> ans;
            for(int i=0;i<cursize;i++){
                TreeNode* tmp=que.front();
                que.pop();//别忘了出队
                ans.push_back(tmp->val);
                //左右孩子入队
                if(tmp->left) que.push(tmp->left);
                if(tmp->right) que.push(tmp->right);
            }
            res.push_back(ans);
        }
        return res;
    }

也可以格外定义level变量,表示层数。

res[level]就是要存放的变量,然后为了保证访问合法性,需要进行判断

 vector<vector<int>> levelOrder(TreeNode* root) {
        using pr=pair<TreeNode*,int>;
        if(root==NULL) return {};
        vector<vector<int>> res;
        //定义队列
        queue<pr> que;
        que.push(make_pair(root,0));
        //遍历
        while(!que.empty()){
            auto [tmp,level]=que.front();
            que.pop();
            //访问合法性检查
            if(level>=res.size()){
                res.push_back({});
            }
            res[level].push_back(tmp->val);
            //左右节点入队
            if(tmp->left) que.push(make_pair(tmp->left,level+1));
            if(tmp->right) que.push(make_pair(tmp->right,level+1));
        }
        return res;
    }

还可以用先序遍历来递归

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        dfs(root,0);
        return res;
    }
private:
    vector<vector<int>> res;
    void dfs(TreeNode* root,int level){
        if(root==NULL) return;
        if(level==res.size()){
            res.push_back(vector<int>());//{}也可以
        }
        //先加入节点
        res[level].push_back(root->val);
        //递归
        dfs(root->left,level+1);
        dfs(root->right,level+1);
    }
};

多叉树先序

589. N 叉树的前序遍历

N插数的数据结构,孩子结点用vector数组存放

class Node {
public:
    int val;
    vector<Node*> children;
    Node() {}
    Node(int _val) {
        val = _val;
    }
    Node(int _val, vector<Node*> _children) {
        val = _val;
        children = _children;
    }
};

本题由于数据:进行序列化表示,所以孩子结点之间存在顺序

方法一:栈非递归实现

由于是先序遍历,先访问结点,再加入孩子。

注意栈的后进先出特性,这里孩子顺序从右至左进入栈

vector<int> preorder(Node* root) {
        vector<int> res;
        if(root==NULL) return res;
        stack<Node*> sta;
        sta.push(root);
        while(!sta.empty()){
            Node* tmp=sta.top();
            sta.pop();
            res.push_back(tmp->val);
            //注意倒着放孩子
            auto it=tmp->children.rbegin();
            for(;it!=tmp->children.rend();it++){
                sta.push(*it);//*it就是tmp->children
            }
        }
        return res;
    }

方法二:递归实现

class Solution {
public:
    vector<int> preorder(Node* root) {
        if(root==NULL) return res;
        res.push_back(root->val);
        //对孩子结点进行递归
        for(auto& x:root->children){
            preorder(x);
        }
        return res;
    }
private:
    vector<int> res;
};

先序和中序遍历创建树

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

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

先序可以确定根节点,中序可以划分左右子树

  • 然后依据中序的左右子树的个数,把先序的左右划分两部分
  • 然后左右两部分的先序又可以确定出根结点,以此类推
class Solution {
public:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        this->pre=preorder;
        this->in=inorder;
        int s1=pre.size();
        int s2=in.size();
        return _build(0,s1-1,0,s2-1);
    }
private:
    vector<int> pre;
    vector<int> in;
    //pre[l1,r1] in[l2,r2]
    TreeNode* _build(int l1,int r1,int l2,int r2){
        //递归结束
        if(l1>r1) return NULL;//先序是结束标志
        //生成根节点
        TreeNode* root=new TreeNode(pre[l1]);
        //去中序找root->val
        int mid=l2;
        while(in[mid]!=root->val) mid++;
        //递归左右两部分  中序:[l2,mid-1] [mid+1,r2]
        int leftsize=mid-l2;
        //先序 [l1+1,x]  x-l1=leftsize [x+1,r1]
        root->left=_build(l1+1,l1+leftsize,l2,mid-1);
        root->right=_build(l1+leftsize+1,r1,mid+1,r2);
        return root;
    }
};

当然先序左右两部分下标还可以依据rightsize来推断

     //中序:[l2,mid-1] [mid+1,r2]
     int leftsize=mid-l2;
     //先序 [l1+1,x]  x-l1=leftsize [x+1,r1]
     int rightsize=r2-mid;
     //先序 [x,r1] r1-x+1=rightsize即:x=r1-rightsize+1
     root->left=_build(l1+1,l1+leftsize,l2,mid-1);
     root->right=_build(r1-rightsize+1,r1,mid+1,r2);

也可以传入指针的引用,函数返回值为void

class Solution {
public:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        this->pre=preorder;
        this->in=inorder;
        int s1=pre.size();
        int s2=in.size();
        TreeNode* mm;
        _build(mm,0,s1-1,0,s2-1);
        return mm;
    }
private:
    vector<int> pre;
    vector<int> in;
    //pre[l1,r1] in[l2,r2]
    void _build(TreeNode*& root,int l1,int r1,int l2,int r2){
        //递归结束
        if(l1>r1) return;//先序是结束标志
        //生成根节点
        root=new TreeNode(pre[l1]);
        //去中序找root->val
        int mid=l2;
        while(in[mid]!=root->val) mid++;
        //递归左右两部分  中序:[l2,mid-1] [mid+1,r2]
        int leftsize=mid-l2;
        //先序 [l1+1,x]  x-l1=leftsize [x+1,r1]
        int rightsize=r2-mid;
        //先序 [x,r1] r1-x+1=rightsize即:x=r1-rightsize+1
        _build(root->left,l1+1,l1+leftsize,l2,mid-1);
        _build(root->right,r1-rightsize+1,r1,mid+1,r2);
    }
};

二叉树查找值为x的结点

分治思想,先找左子树,找不到再去找右子树

TreeNode* findNode(TreeNode* root,int x){
    if(root==NULL) return NULL;
    if(root->val==x)
      return root;//找到了
    //先去左子树找,找不到去右子树找
    TreeNode *leftSide = findNode(root->left, x);
    TreeNode *rightSide = findNode(root->right, x);
    if(leftSide){
      return leftSide;
    }
    else{//rightSide
      return rightSide;
    }
}

//先去左子树找,找不到去右子树找

return findNode(root->left, x)?findNode(root->left, x):findNode(root->right, x);
在这里插入图片描述

也可以简写成一句话

return findNode(root->left,x)?findNode(root->left,x):findNode(root->right,x); 

求堂兄弟节点

993. 二叉树的堂兄弟节点

这道题说白了就是两个子问题的集合

  • 求两个结点的父节点
  • 求两个结点的自身深度
如何求父节点

递归

区别就在于判断的是当前结点的孩子结点是否为x,避免NULL->val,需要先判断存在

TreeNode* findParent(TreeNode* root,int x){
        if(root==NULL) return NULL;
        //左右子树存在且值为x则找到父节点
        if(root->left){
            if(root->left->val==x) return root;
        }
        if(root->right){
            if(root->right->val==x) return root;
        }
        if(findParent(root->left,x)){
            return findParent(root->left,x);
        }else{
            return findParent(root->right,x);
        }
    }

层次遍历求

//层次遍历
    TreeNode* findParent(TreeNode* root,int x){
        if(root==NULL) return NULL;
        queue<TreeNode*> que;
        que.push(root);
        while(!que.empty()){
            TreeNode* tmp=que.front();
            que.pop();
            if(tmp->left){
                //判断左孩子值
                if(tmp->left->val==x){
                    return tmp;
                }
                que.push(tmp->left);
            }
             if(tmp->right){
                //判断右孩子值
                if(tmp->right->val==x){
                    return tmp;
                }
                que.push(tmp->right);
            }
        }
        return NULL;
    }

栈的先序遍历求

//先序遍历
    TreeNode* findParent(TreeNode* root,int x){
        if(root==NULL) return NULL;
        stack<TreeNode*> sta;
        sta.push(root);
        while(!sta.empty()){
            TreeNode* tmp=sta.top();
            sta.pop();
            //栈后进先出,所以先右后左
             if(tmp->right){
                if(tmp->right->val==x){
                    return tmp;
                }
                sta.push(tmp->right);
            }
            if(tmp->left){
                if(tmp->left->val==x){
                    return tmp;
                }
                sta.push(tmp->left);
            }
            
        }
        return NULL;
    }
如何求深度

递归

int nodeLevel(TreeNode* root,int x,int level){
        if(root==NULL) return -1;//表示不存在
        if(root->val==x) return level;
        int l=nodeLevel(root->left,x,level+1);
        int r=nodeLevel(root->right,x,level+1);
        if(l!=-1){
            return l;
        }
        if(r!=-1){
            return r;
        }
        return -1;
    }

非递归

int nodeLevel(TreeNode* root,int x){
        if(root==NULL) return -1;
        using pr=pair<TreeNode*,int>;
        queue<pr> que;
        que.push(make_pair(root,1));
        while(!que.empty()){
            auto[node,level]=que.front();
            que.pop();
            //当前值满足条件
            if(node->val==x) return level;
            if(node->left){
                que.push(make_pair(node->left,level+1));
            }
            if(node->right){
                que.push(make_pair(node->right,level+1));
            }
        }
        return -1;
    }

如果queue不放pair对

 int nodeLevel(TreeNode* root, int x) {
        if (root == NULL)
            return -1;
        queue<TreeNode*> que;
        que.push(root);
        int level = 0;
        while (!que.empty()) {
            int curSize = que.size();
            level++;//层加一
            for (int i = 0; i < curSize; i++) {
                TreeNode* node = que.front();
                que.pop();
                // 当前值满足条件
                if (node->val == x)
                    return level;
                if (node->left) {
                    que.push(node->left);
                }
                if (node->right) {
                    que.push(node->right);
                }
            }
        }
        return -1;
    }
本题解法

可以合并一个程序,由于返回结点和深度,所以用私有变量记录,然后函数返回值为void

class Solution {
public:
    bool isCousins(TreeNode* root, int x, int y) {
        lx = ly = 0;
        tx = NULL;
        ty = NULL;
        find(root, NULL, x, y, 1);
        //不存在父节点
        if (!tx || !ty)
            return false;
        //父节点不同,但深度相同
        if (tx != ty && lx == ly)
            return true;
        else
            return false;
    }

private:
    TreeNode *tx, *ty;
    int lx, ly;
    void find(TreeNode* root, TreeNode* par, int x, int y, int level) {
        if (root == NULL)
            return;
        if (root->val == x) {
            lx = level;
            tx = par;
        }
        if (root->val == y) {
            ly = level;
            ty = par;
        }
        find(root->left, root, x, y, level + 1);
        find(root->right, root, x, y, level + 1);
    }
};

提前判断再递归也可以

if(root->left){
     find(root->left, root, x, y, level + 1);
}
if(root->right){
     find(root->right, root, x, y, level + 1);
}

当然也可以用pair对返回深度和父节点,然后调用两次

class Solution {
public:
    bool isCousins(TreeNode* root, int x, int y) {
        pr.first = NULL;
        pr.second = 0;
        auto [k1, v1] = find(root, NULL, x, 1);
        auto [k2, v2] = find(root, NULL, y, 1);
        if(k1!=k2 && v1==v2) return true;
        else return false;
    }
private:
    pair<TreeNode*, int> pr;
    pair<TreeNode*, int> find(TreeNode* root, TreeNode* par, int v, int level) {
        if (root == NULL)
            return pr;
        if (root->val == v) {
            //pr.first = par;
            //pr.second = level;
            //pr=make_pair(par,level);
            pr={par,level};
            return pr;
        }
        find(root->left, root, v, level + 1);
        find(root->right, root, v, level + 1);
        return pr;
    }
};

pair的赋值

pair<> =make_pair();

pair<>={};

pair<> fisrst,second单独赋值

求最近公共祖先

236. 二叉树的最近公共祖先

解法一:利用map记录二叉树的所有结点的父节点(该题无重复元素),然后把其中一个结点的路径用set记录下来,那么遍历另一个节点时,只要相遇(就是该节点第一次遍历到上一个结点走过的,也就是set记录过),则该点就是所求点。

在这里插入图片描述

比如对于5:5的父节点是9,9的父节点是7,组成路径:597

向上找父节点时,只要遇到红色结点就是所求。

比如:3第一次遇到是7,则:3和6的最近公共子结点是7

求所有结点父节点

一定要:mp【节点值】=父节点,因为这样mp[mp[x]->val]可以不断求父节点

 //mp[结点]=深度
    unordered_map<int,TreeNode* > mp;
    void findParent(TreeNode* root){
        if(root==NULL) return;
        //mp[结点值]=父节点指针
        if(root->left){
            mp[root->left->val]=root;
        }
        if(root->right){
            mp[root->right->val]=root;
        }
        findParent(root->left);
        findParent(root->right);
    }

在这里插入图片描述

输出结果:

   findParent(root);
    for(auto& [k,v]:mp){
       cout<<k<<"的父节点是"<<v->val<<endl;
    }

在这里插入图片描述

       cout<< mp[11]->val<<endl;//5
       cout<<mp[mp[11]->val]->val<<endl;//2
       cout<< mp[mp[mp[11]->val]->val]->val<<endl;//1

同样可以求深度

//mp[结点]=深度
    unordered_map<TreeNode* ,int> mp;
    void findlevel(TreeNode* root,int level){
        if(root==NULL) return;
        mp[root]=level;
        findlevel(root->left,level+1);
        findlevel(root->right,level+1);
    }
本题解法
class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        findFather(root);
        // 从p开始,将其所有父节点存入set中
        set<int> st;
        //st.insert(root->val);
        while (p) {
            st.insert(p->val);
            p = mp[p->val];
        }
        // 从q开始,查找set第一个存在的
        while (q) {
            if(st.count(q->val)){
                return q;
            }
            q=mp[q->val];
        }

        return NULL;
    }

private:
    // mp[节点值]=父节点
    unordered_map<int, TreeNode*> mp;
    void findFather(TreeNode* root) {
        if (root == NULL)
            return;
        if (root->left) {
            mp[root->left->val] = root;
            findFather(root->left);
        }
        if (root->right) {
            mp[root->right->val] = root;
            findFather(root->right);
        }
    }
};
    // 从q开始,查找set第一个存在的
     while (q) {
         if(st.count(q->val)){
             return q;
         }
         q=mp[q->val];
     }

可以改写

    // 从q开始,查找set第一个存在的
     while (!st.count(q->val)) {
         q = mp[q->val];
     }
     return q;

加入路径也可以依据值:

     set<int> st;
     st.insert(root->val);
     while (p->val!=root->val) {
         st.insert(p->val);
         p = mp[p->val];
     }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/457755.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Ansible自动化运维Inventory与Ad-Hoc

前言 自动化运维是指利用自动化工具和技术来简化、自动化和优化IT基础设施的管理和运维过程&#xff0c;从而提高效率、降低成本&#xff0c;并减少人为错误。在当今复杂的IT环境中&#xff0c;自动化运维已经成为许多组织和企业提高生产力和保证系统稳定性的重要手段。Ansibl…

<JavaEE> 了解网络层协议 -- IP协议

目录 初识IP协议 什么是IP协议&#xff1f; IP协议中的基础概念 IP协议格式 图示 4bit版本号&#xff08;version&#xff09; 4bit头部长度&#xff08;headerlength&#xff09; 8bit服务类型&#xff08;TypeOfService&#xff09; 16bit总长度&#xff08;total l…

Chrome的V8引擎 和操作系统交互介绍

Chrome的V8引擎是一个用C编写的开源JavaScript和WebAssembly引擎&#xff0c;它被用于Chrome浏览器中&#xff0c;以解释和执行JavaScript代码。V8引擎将JavaScript代码转换为机器代码&#xff0c;这使得JavaScript能够以接近本地代码的速度运行。 V8引擎与操作系统的交互主要体…

开源免费CasaOS:轻松打造高效便捷的家庭云生活新体验

一、引言 随着科技的不断发展&#xff0c;家庭云系统逐渐成为现代家庭生活中的重要组成部分。CasaOS作为一款简单易用的开源家庭云系统&#xff0c;受到了广大用户的青睐。其简洁明了的界面设计&#xff0c;使得即使没有任何技术背景的用户也能轻松上手。本文将详细介绍CasaOS…

C# WPF编程-概述

C# WPF编程-基础知识 概述WPF高级API硬件加速与WPF分辨率无关性WPF体系结构WPF的主要类WPF工具包Visual Studio IDE 概述 WPF(Windows Presentation Foundation)是用于Windows的现代图形显示系统。引入了“内置硬件加速”和“分辨率无关”等创新功能。 WPF高级API 类似Web的…

【Docker】-- 使用docker部署Springboot+vue项目到虚拟机

虚拟机系统&#xff1a;CentOS 7 连接虚拟机工具&#xff1a;MobaXterm 以下在安装好虚拟机和docker基础上操作。 安装docker&#xff1a;【Docker】-- 如何安装docker-CSDN博客 一、部署Mysql 1、安装mysql docker中执行&#xff1a; docker run -d \--name mysql \-p 3…

Python + Jmeter 实现自动化性能压测

Step01: Python脚本开发 文件路径&#xff1a;D://wl//testproject//Fone-grpc//project1//test_client.py Python 脚本作用&#xff1a; 通过 grpc 调用底层 c 的接口&#xff0c;做数据库的数据插入与查询操作&#xff0c;然后将返回的结果进行拼接与输出。 2.代码里面将…

基于SpringBoot+MYSQL+Vue的校园管理系统

目录 1、前言介绍 2、主要技术 3、系统流程分析 3.1、操作流程 3.2、添加信息流程 3.3、删除信息流程 4、系统设计 4.1 系统体系结构 4.2开发流程设计 4.3 数据库设计原则 4.4 数据表 5、运行截图(部分) 5.1管理员功能模块 5.2用户功能模块 5.3院校管理员功能模块…

使用耳机壳UV树脂制作私模定制耳塞的价格如何呢?

耳机壳UV树脂制作私模定制耳塞的价格因多个因素而异&#xff0c;如材料、工艺、设计、定制复杂度等。 根据我目前所了解到的信息&#xff0c;使用UV树脂制作私模定制耳塞的价格可能在数百元至数千元不等。具体价格还需根据用户的需求和预算进行综合考虑。 如需获取更准确的报…

微信小程序仿QQ头像轮播效果

1、效果图 2、效果流程分析 1、第1个头像大小从1到0缩小的同时&#xff0c;第2个头像左移 2、上面动画结束后&#xff0c;延迟50ms&#xff0c;第3个头像从0到1放大 3、把头像列表顺序前移一位&#xff0c;并重置轮播状态&#xff0c;以此达到一个循环。然后继续第一个步骤 …

STL——map set

文章将解决一下几个问题&#xff1a; 1.什么是set 2.什么是map 3.set应用场景 4.map应用场景 序列式容器和关联式容器 数据结构有序列式容器和关联式容器&#xff0c;序列式容器一般有vector,list,deque…&#xff0c;但关联式容器中就有map&#xff0c;关联式容器也是用来存…

Java实现知乎热点小时榜爬虫

1.效果演示 1.1 热点问题列表 启动程序后&#xff0c;自动展示热点问题&#xff0c;并等待终端输入 1.2 根据序号选择想看的热点问题 输入问题序号&#xff0c;展示回答内容 1.3 退出 输入q即可退出程序 2.源码 2.1 pom.xml <?xml version"1.0" enco…

鸿蒙Harmony应用开发—ArkTS声明式开发(容器组件:FlowItem)

瀑布流组件的子组件&#xff0c;用来展示瀑布流具体item。 说明&#xff1a; 该组件从API Version 9开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。仅支持作为Waterflow组件的子组件使用。 子组件 支持单个子组件。 接口 FlowItem() 使…

[数据集][目标检测]零售柜零食检测数据集VOC+YOLO格式5422张113类

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;5422 标注数量(xml文件个数)&#xff1a;5422 标注数量(txt文件个数)&#xff1a;5422 标注…

html5cssjs代码 018颜色表

html5&css&js代码 018颜色表 一、代码二、效果三、解释 这段代码展示了一个基本的颜色表&#xff0c;方便参考使用&#xff0c;同时也应用了各种样式应用方式。 一、代码 <!DOCTYPE html> <html lang"zh-cn"> <head><title>编程笔记…

Redis开发规范与性能优化(二)

开发规范与性能优化 3.客户端使用 1.【推荐】避免多个应用使用一个Redis示例 正例:不相干的业务拆分&#xff0c;公共数据库做服务化 2.【推荐】使用带有连接池的数据库&#xff0c;可以有效控制链接&#xff0c;同时提高效率&#xff0c;标准使用方式如代码所示 public c…

AMD芯片使用Stable-Diffusion

AMD芯片使用Stable-Diffusion 由于A卡的Stable Diffusion工具的逐步完善&#xff0c;之前只能使用CPU跑&#xff0c;现在已支持AMD显卡进行AI绘图。 下载 官网链接&#xff1a;https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Install-and-Run-on-AMD-GPUs 按…

LAMP网站部署(Discuz论坛网站部署)

目录 mysql命令 语法 选项 参数 实例 安装php 安装Mariadb 关掉防火墙和selinux 启动HTTP服务 初始化数据库 查看数据库是否创建成功 修改HTTP的配置文件 浏览器打开 将以下所有目录都加上权限 最后首页效果 mysql命令 是MySQL数据库服务器的客户端工具&#xff0c;它工作在命…

【Linux下qt软件安装打包附带问题: dpkg: error processing package xxxx +解决方式+自我尝试+记录】

【Linux下qt软件安装打包附带问题&#xff1a; dpkg: error processing package xxxx 解决方式自我尝试记录】 1、前言2、实验环境3、问题说明4、我的努力与查到解决的方式&#xff08;1&#xff09;补充两个文件&#xff0c;让软件正常执行&#xff08;2&#xff09;尝试修复d…

springboot+vue学生选课系统 java+ssm+idea+_mysql

系统包含三种角色&#xff1a;管理员、老师、学生&#xff0c;系统分为前台和后台两大模块&#xff0c;主要功能如下。 ide工具&#xff1a;IDEA 或者eclipse 编程语言: java 学生网上选课系统可以实现教室管理&#xff0c;老师管理&#xff0c;课程管理&#xff0c;教学计划管…