算法-树

算法-树

BST

BST节点模板

struct TreeNode{
	int val;
	TreeNode *left,*right;
	TreeNode():val(0),left(nullptr),right(nullptr){}
	TreeNode(int x):val(x),left(nullptr),right(nullptr){}
	TreeNode(int x,TreeNode* l,TreeNode* r):val(x),left(l),right(r){}
}

1. 2021-4-18 不同的二叉搜索树 II

题目:

给定一个整数 n,生成所有由 1 … n 为节点所组成的 二叉搜索树 。

在这里插入图片描述

思路
最优二叉树

和最优二叉树的思路很像,本质上是一道树形DP。

通过BST中序遍历的性质,将i从l->r分别取根。
随后先遍历左右两节点获得vector也就是子节点的各种可能。

随后二重循环分别从左右子节点里选择节点,新建根,指向左右子节点,插入到vector中。

最后返回vector。

代码:

class Solution {
public:
    vector<TreeNode*> generateTrees(int n) {
        return dfs(1,n);
    }
    vector<TreeNode*> dfs(int s,int e){
        if(s>e)return {nullptr};
        vector<TreeNode*> ans;
        for(int i=s;i<=e;++i){
            auto left=dfs(s,i-1);
            auto right=dfs(i+1,e);
            for(auto &l:left){
                for(auto &r:right){
                    TreeNode *root=new TreeNode(i);
                    root->left=l;
                    root->right=r;
                    ans.push_back(root);
                }
            }
        }
        return ans;
    }
};

2. 2021-4-18 恢复二叉搜索树

题目:

给你二叉搜索树的根节点 root ,该树中的两个节点被错误地交换。请在不改变其结构的情况下,恢复这棵树。

进阶:使用 O(n) 空间复杂度的解法很容易实现。你能想出一个只使用常数空间的解决方案吗?

在这里插入图片描述

在这里插入图片描述

思路

考的还是BST中序遍历的性质。

如果有两个节点出错,那么在中序遍历时出错中大的那个一定先遍历到,出错中小的那个一定后遍历到。

如果设置一个prev和now,只需要当prev>now的时候,就检测到了出错节点。
第一次prev>now,出错的是prev。第二次prev>now,出错的是now。

另外注意两点:
prev的设置应该是左 根 右的根结束操作之时,因为中序遍历是在最左节点开始进行的,prev自然是最左退出来之后的根处。
另外,如果出错的节点相连,那么second就是第一次检测到的now。

代码:

class Solution {
public:
    TreeNode* first=nullptr;
    TreeNode* second=nullptr;
    TreeNode* prev=nullptr;
    void dfs(TreeNode *now){
        if(!now)return;
        dfs(now->left);

        if(prev){
            if(prev->val>now->val){
                if(first==nullptr)first=prev;
                second=now;
            }
        }
        
        prev=now;
        dfs(now->right);
    }
    void recoverTree(TreeNode* root) {
        if(!root)return;
        dfs(root);
        if(first&&second)swap(first->val,second->val);
    }
};

3. 2021-4-18 先序中序构造BST 后序中序构造BST

题目:

根据一棵树的前序遍历与中序遍历构造二叉树。
根据一棵树的后序遍历与中序遍历构造二叉树。

注意:
你可以假设树中没有重复的元素。

例如,给出

前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]

返回如下的二叉树:

3
/ \
9  20
/  \
15   7

例如,给出

中序遍历 inorder = [9,3,15,20,7]
后序遍历 postorder = [9,15,7,20,3]

返回如下的二叉树:

3
/ \
9  20
/  \
15   7

思路

用到的是前序和后序对应中序的性质:

前序从左到右index对应的值,其值在中序中对应的index,index左边是左子树,右边是右子树。
后序从右到左index对应的值,其值在中序中对应的index,index左边是左子树,右边是右子树。

先通过hash将inorder[i]映射到i,随后全局维护一个preindex从0->n-1。
再维护一个postindex从n-1->0。

开始构建以后,val=preorder[preindex]/postorder[postorder],通过val可以快速定位到inorder的indexorder。
随后创建根节点进行构建就可以。

需要注意的是preorder对于preindex的++顺序是从左子树到右子树。
而postorder对于postorder的--顺序是从右子树到左子树。

代码:

//先序中序构造BST
class Solution {

public:
    unordered_map<int,int> dic;
    int preindex=0;
    TreeNode* dfs(vector<int>& preorder, vector<int>& inorder,int l,int r){
        if(l>r)return nullptr;
        int val=preorder[preindex];
        int index=dic[val];
        preindex++;
        TreeNode* root=new TreeNode(val);
        auto ll=dfs(preorder,inorder,l,index-1);
        auto rr=dfs(preorder,inorder,index+1,r);
        root->left=ll;
        root->right=rr;
        return root;
    }
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        for(int i=0;i<inorder.size();++i){
            dic[inorder[i]]=i;
        }
        return dfs(preorder,inorder,0,preorder.size()-1);
    }
};

//后序中序构造BST
class Solution {
public:
    unordered_map<int,int> dic;
    int postindex=0;
    TreeNode* dfs(vector<int>& inorder, vector<int>& postorder,int l,int r){
        if(l>r)return nullptr;
        int val=postorder[postindex];
        int index=dic[val];
        postindex--;
        TreeNode *root=new TreeNode(val);
        auto rr=dfs(inorder,postorder,index+1,r);
        auto ll=dfs(inorder,postorder,l,index-1);
        root->left=ll;
        root->right=rr;
        return root;
    }
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        postindex=postorder.size()-1;
        for(int i=0;i<postorder.size();++i){
            dic[inorder[i]]=i;
        }
        return dfs(inorder,postorder,0,postorder.size()-1);
    }
};

4. 2021-5-9 满二叉树的递归

题目:

第一行输入0,第二行将0变为01,得到01;第三行将0变换为01,将1变换为10,得到0110;之后的每一行都是通过将其前一行中的0变换为01,将1变换为10而得到。
现给定N,K,其中1≤N≤30,1≤K≤2^(30-1)。输出第N行的第K个字符。

输入输出

输入示例:

5 10

输出示例:

0

思路

是一棵满二叉树,前一半是上一层,后一半是上一层按位取非。
因此设置递归,find(n,k,now) now一开始为0。
当n=1时,返回now。
如果是前一半,也就是k<=(1<<n-2) (注意这里n-2 因为第一层是0,另外与上一层相比),返回find(n-1,k,now==0?0:1)即可。
如果是后一半,需要将k减去前一半的数字,再按位取反 find(n-1,k-(1<<n-2) ,now==0?1:0)即可。

在这里插入图片描述

代码:

int find(int n, int k, int now)
{
	if (n == 1)return now;
	else
	{
		if (k <= (1 << (n - 2)))return find(n - 1, k, now == 0 ? 0 : 1);
		else return find(n - 1, k - (1 << (n - 2)), now == 0 ? 1 : 0);
	}
	
}

int main()
{
	int n, k;
	cin >> n >> k;
	cout << find(n, k, 0);
	
}

二叉树结构问题

1. 2021-4-18 二叉树展开成链表

题目:

给你二叉树的根结点 root ,请你将它展开为一个单链表:

展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。

展开后的单链表应该与二叉树 先序遍历 顺序相同。
在这里插入图片描述

思路

第一直观感受就是后序遍历。
root->right=left;left->right=right;
出现两个问题:首先是left直接上要先->right到最右节点。
其次是左节点需要指向nullptr。

代码:

class Solution {
public:
    TreeNode* dfs(TreeNode* root){
        if(!root)return nullptr;
        auto l=dfs(root->left);
        auto r=dfs(root->right);
        if(l){
            root->right=l;
            while(l->right!=nullptr)l=l->right;
            l->right=r;
        }
        root->left=nullptr;
        return root;
    }
    void flatten(TreeNode* root) {
        if(!root)return;
        dfs(root);
    }
};

2. 2021-4-18 二叉树最近公共祖先

题目:

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
在这里插入图片描述
在这里插入图片描述

思路

其实p和q所在的位置对于当前root只有三种情况:
全在root左子树上,全在root右子树上,此时为了最近祖先的概念得进入子树。
还有一种情况就是分别在左右子树上,此时直接返回root即可。

判断是否在左右子树的方法,即通过后序遍历,root为p或者q时返回root即可。
root遍历完左右子树之后如果收到的是nullptr,说明这边子树没有p和q。

代码:

class Solution {
public:

    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(!root)return nullptr;
        if(root==p||root==q)return root;
        TreeNode* l=lowestCommonAncestor(root->left, p, q);
        TreeNode* r=lowestCommonAncestor(root->right, p, q);
        if(l==nullptr)return r;
        if(r==nullptr)return l;
        if(l&&r)return root;
        return nullptr;
    }
};

3. 2021-4-18 满二叉树的next指针

题目:

给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:

struct Node {
  int val;
  Node *left;
  Node *right;
  Node *next;
}

填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。

初始状态下,所有 next 指针都被设置为 NULL。

进阶:

你只能使用常量级额外空间。
使用递归解题也符合要求,本题中递归程序占用的栈空间不算做额外的空间复杂度。

在这里插入图片描述

思路

一道少见的先序遍历递归题。
其实最难的就是连接不同子树的next。
由于满二叉树的性质,我们在root->left->next=root->right之后可能无从下手。
实际上我们需要连到root->next->left。
那么只需要先序遍历,如果root->next存在,root->right-next=root->next->left即可。
但这个思路其实有点小问题,后面一道题会出现。

代码:

class Solution {
public:
    Node* connect(Node* root) {
        if(!root)return nullptr;
        if(!root->left)return root;
        root->left->next=root->right;
        if(root->next)root->right->next=root->next->left;
        connect(root->left);connect(root->right);
        return root;
    }
};

4. 2021-4-18 一般二叉树的next指针

题目:

给定一个二叉树

struct Node {
  int val;
  Node *left;
  Node *right;
  Node *next;
}

填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。

初始状态下,所有 next 指针都被设置为 NULL。

进阶:

你只能使用常量级额外空间。
使用递归解题也符合要求,本题中递归程序占用的栈空间不算做额外的空间复杂度。

在这里插入图片描述

思路

与上题不同,子节点的数量不确定。

在最后一层可能会出现最左连到最右。
但本质还是一样,我们要通过next找到最右的父节点。
通过循环即可,如果next过程中有子树就停下来。

如果只是这样做是有问题的,在满二叉树中我们忽略了一个细节。
即next的构建应该是从右至左的,因为next是从左至右的,我们默认右边的next已经构建完成。
实际上当不是满二叉树时,必须要根右左的方式来搭建next。

代码:

class Solution {
public:
    Node* connect(Node* root) {
        if(!root)return nullptr;
        if(!root->left&&!root->right)return root;
        Node *child;
        if(root->left&&root->right){
            root->left->next=root->right;
            child=root->right;
        }
        else {
           child=root->left?root->left:root->right;
        }
        Node* tmp=root->next;
        while(tmp&&!tmp->left&&!tmp->right)tmp=tmp->next;
        if(!tmp)child->next=nullptr;
        else if(tmp->left)child->next=tmp->left;
        else if(tmp->right)child->next=tmp->right;
        connect(root->right);
        connect(root->left);
        
        return root;
    }
};

5. 2021-4-20 最大路径和

题目:

路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。

路径和 是路径中各节点值的总和。

给你一个二叉树的根节点 root ,返回其 最大路径和 。

在这里插入图片描述
在这里插入图片描述

思路

首先要搞清楚,一条路径一定会经过当前节点,但是不知道会经过多少左节点和右节点,我们需要清楚的是lmax和rmax。	
由于val可能<0,我们每次将路径和的更新放在当前节点上,即lmax+root->val+rmax,并要求lmax>=0且rmax>=0。
每次返回值是子节点->父节点的最大路径。
lmax=max(0,dfs(root->lmax))
rmax=max(0,dfs(root->rmax))
ans=lmax+root->val+rmax
此时返回值即是root->val+max(lmax+rmax)了。

代码:

class Solution {
public:
    int ans=-10000;
    int dfs(TreeNode *root){
        if(!root)return 0;
        int lmax=max(dfs(root->left),0);
        int rmax=max(dfs(root->right),0);
        ans=max(ans,root->val+lmax+rmax);
        return max(lmax,rmax)+root->val;
    }
    int maxPathSum(TreeNode* root) {
        if(!root)return 0;
        dfs(root);
        return ans;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值