算法之树题型总结

1.非递归实现先序、中序、后序遍历

先序遍历:如果有左子树就一直入栈并且访问,没有的话就需要出栈一个节点(已经访问过),并且遍历它的右子树

class Solution(object):
    def preorderTraversal(self, root):
        stack = []
        res = []
        node = root
        while(stack!=[] or node):
            #遍历左子树
            while(node):
                res.append(node.val)
                stack.append(node)
                node = node.left
            #遍历右子树
            node = stack[-1]
            stack.pop()
            node = node.right  
        return res

中序遍历:如果有左子树就一直入栈,如果没有就出栈一个元素并且访问,并且遍历它的右子树

class Solution(object):
    def inorderTraversal(self, root):
        stack = []
        res = []
        node = root
        while(stack!=[] or node):
            while(node):
                stack.append(node)
                node = node.left
            node = stack[-1]
            stack.pop()
            res.append(node.val)
            node = node.right
        return res

后序:先要遍历完成左子树和右子树之后才能打印根节点,所以要保留根节点到左右子树被访问后,如果不加判断打印完根节点就又会遍历右子树造成无限循环,需要一个指针指向上一次访问的节点

class Solution(object):
    def postorderTraversal(self, root):
        res = []
        stack = []
        node = root
        last = None
        while(stack!=[] or node):
            while(node):
                stack.append(node)
                node = node.left
            node = stack[-1]
            if(node.right and node.right!=last):#node.right为空时候应该直接打印
                node = node.right
                continue
            if(node.right==None or node.right==last):
                last = node
                res.append(node.val)
                stack.pop()
                node = None #不需要在进入左子树,向上回溯
        return res

2.找到二叉树中最大的搜索二叉子树

思路:递归返回最大搜索二叉子树的头节点,注意判断条件要更新节点的max和min值,if  left=ldfs & right=rdfs & v>lmax & v<rmax,在判断是否当前节点可以加一;否则就返回左右子树num大的头节点。

需要注意返回值:为当前节点,参数res[size,min,max]

public TreeNode biggestSubBST(TreeNode head) {
    int[] record = new int[3];

    return posOrder(head, record);
}
//整体过程是后序遍历
public TreeNode posOrder(TreeNode head, int[] record) {
    if (head == null) {
        record[0] = 0;
        record[1] = Integer.MAX_VALUE;
        record[2] = Integer.MIN_VALUE;
        return null;
    }
    int value = head.value;
    TreeNode left = head.left;
    TreeNode right = head.right;

    TreeNode lBST = posOrder(left, record); //左子树上最大搜索二叉树的头节点
    int lSize = record[0];
    int lMin = record[1];
    int lMax = record[2];

    TreeNode rBST = posOrder(right, record); //右子树上最大搜索二叉树的头节点
    int rSize = record[0];
    int rMin = record[1];
    int rMax = record[2];

    record[1] = Math.min(lMin, value); //最小值
    record[2] = Math.max(rMax, value); //最大值
    //以node为头的整棵树都为搜索二叉树
    if (left == lBST && right == rBST && lMax < value && value < rMin) {
        record[0] = lSize + rSize + 1; //节点数
        return head;
    }
    //以node的左子树或者右子树为头的子树为搜索二叉树
    record[0] = Math.max(lSize, rSize); //节点数
    return lSize > rSize ? lBST : rBST;
}

3.最近公共祖先

给定两个节点,查找这两个节点的最近公共祖先。

是二叉查找树:划分区间,当前节点的值会落在区间的中间还是左边还是右边。

不是二叉查找树但是二叉树且节点有父节点指针:转化成两条链表找第一个公共节点。

不是二叉查找树但是二叉树且节点无父节点指针:递归的找到两个节点找到后,返回目标节点,如果第一次该节点的左右子节点返回目标节点的话,那么这个节点就是最近公共祖先。

class Solution:
    def lowestCommonAncestor(self, root, p, q):
        """
        :type root: TreeNode
        :type p: TreeNode
        :type q: TreeNode
        :rtype: TreeNode
        """
        if (not root) or root==p or root==q:
            return root
        left = self.lowestCommonAncestor(root.left, p, q)
        right = self.lowestCommonAncestor(root.right, p, q)
        if left and right:
            return root
        else:
            return left if left else right

是多叉树:转化为两条链表,寻找最后一个相交的节点。

ps:多叉树你还是不能通过回溯来判断的,太复杂实现不了的。

扩展:没有给出父节点的情况下,计算两个节点的距离(边数)

思路:先找到最近公共祖先,然后d(r1,r2)=d(r1,root)+d(r2,root)-2d(root,parent)

4.二叉树的按层打印

思路:使用两个变量,记录当前层的尾节点,遍历过程中记录下一层的尾节点。

扩展:给定一个二叉树,在树的每一行找到最右边的值

思路:使用队列按层遍历,记录当前层的剩余节点数,和下一层节点的数量;当前层节点数为0的时候更新一下返回值即可。

扩展:锯齿形打印

思路:有两个状态需要切换,

从左到右时(就是头部删除尾部加入),从右向左时(需要变为尾部删除,头部加入);

每一层都要记录当前层的尾节点,以及在遍历当前层的过程中维护下一层的尾节点;

5.判断二叉树是否是平衡二叉树

思路:遍历每个节点,对每个节点计算左子树和右子树的层数,来判断是不是平衡树(左子树和右子树必须是平衡树)

ps:要知道的是左子树和右子树的层数,所以无法使用递归回溯来完成,必须得使用两个递归来完成!

class Solution(object):
    #第一种写法
    def isBalanced(self, root):
        if(root==None):
            return True

        def getDepth(root):
            if(root==None):
                return 0
            return max(getDepth(root.left)+1,getDepth(root.right)+1)
        return abs(getDepth(root.left)-getDepth(root.right))<=1 \
                and self.isBalanced(root.left) and self.isBalanced(root.right)

ps:可以使用字典缓存来进行优化;

扩展一:判断一棵树是否是搜索二叉树

思路一:使用中序递归时,每次比较前继节点即可,中序遍历是一个有序的序列。

    //中序遍历的方法实现
    public static Node prev=null;
    public static boolean isBST(Node root)
    {
        if(root != null)
        {
            if(!isBST(root.left))
                return false;
            if(prev != null && root.value < prev.value)
                return false;
            prev = root;
            if(!isBST(root.right))
                return false;
         }
        return true;
    }
public:
    bool cece(TreeNode* root) {
        TreeNode* p = root;
        stack<TreeNode*> st;
        long inorder = LONG_MIN;
        while(p || !st.empty()){
            while(p){
                st.push(p);
                p = p->left;
            }
            p = st.top();
            st.pop();
            if(p->val <= inorder) return false;
            inorder = p->val;
            p = p->right;
        }
        return true;
    }

思路二:使用后序遍历也可以做,找到左子树的最大值和右子树的最小值即可。

思路三:使用中序遍历保存下来,然后判断是否有序;

扩展二:已知一棵完全二叉树,求其结点个数,要求时间复杂度0(N)

思路:每次递归当前节点右子树最左边的长度,如果等于高度左子树就可以计算继续递归右子树,否则计算右子树继续递归左子树;

	public static int nodeNum(Node head) {
		if (head == null) {
			return 0;
		}
		return bs(head, 1, mostLeftLevel(head, 1));
	}

	public static int bs(Node node, int l, int h) {
		if (l == h) {
			return 1;
		}
		if (mostLeftLevel(node.right, l + 1) == h) {
			return (1 << (h - l)) + bs(node.right, l + 1, h);
		} else {
			return (1 << (h - l - 1)) + bs(node.left, l + 1, h);
		}
	}

	public static int mostLeftLevel(Node node, int level) {
		while (node != null) {
			level++;
			node = node.left;
		}
		return level - 1;
	}

6.通过先序中序数组生成后序数组

思路:每次确定根节点然后划分左右子树,每次向后序数组中加入根节点主要需要确定好放入的位置。

7.判断一颗二叉树是否为对称的结构。

思路:使用了先序遍历的写法,使用正常的前序遍历,在使用一个对称的前序遍历,一块进行遍历;

class Solution {
public:
    bool isSymmetric(TreeNode* root) {
        if(root==nullptr) return true;
        return bfs(root->left,root->right);
    }
    bool bfs(TreeNode * l,TreeNode * r){
        if(l==nullptr && r==nullptr) return true;
        if(l==nullptr || r==nullptr) return false;
        if(l->val==r->val){
            return bfs(l->left,r->right) && bfs(l->right,r->left);
        }
        else{
            return false;
        }
    }
};

扩展:使用非递归的写法;

可以使用先序/中序/后序遍历,使用两个栈,一个栈先遍历左子树在遍历右子树,一个栈先遍历右子树在遍历左子树即可;

8.二叉搜索树的后序遍历序列

通过这个序列来判断这棵树是否是二叉搜索树

思路:每次确定对应的根节点,从右往左找到第一个小于根节点的节点以此来分割点,如果右子树都符合条件在判断左子树和右子树。

    bool VerifySquenceOfBST(vector<int> sequence) {
        if(sequence.empty() == true)
        {
            return false;
        }
        return PanDuanTree(sequence,0,sequence.size()-1);
    }
    bool PanDuanTree(vector<int>seq,int begin,int end)
    {
        int root = seq[end];
        int i;
        for(i = begin;i < end ;i++)
        {
            if(seq[i] > root)
            {
                break;
            }
        }
        int mid = i;
        for(int j = mid;j < end ;j++)
        {
            if(seq[j] < root)
            {
                return false;
            }
        }
        bool left = true;
        bool right = true;
        if(mid > begin)
        {
            left = PanDuanTree(seq,begin,mid-1);
        }
        if(mid < end-1)
        {
            right = PanDuanTree(seq,mid,end-1);
        }
        return left && right;
    }

9.在二叉排序树中找出第一个大于中间值的节点

思路:因为要求中间值,所以先找到min和max,遍历二叉排序树,如果当前节点小于中间值就在右子树上继续,如果当前节点大于等于中间值就在右子树找最左边的节点。

10.在二叉树中找出路径最大和

思路:后序遍历,返回值为以当前节点为止的最长路径,需要在这个过程中更新最长路径;

    public int maxPathSum(TreeNode root) {
        // write your code here
        if(root==null){
            return 0;
        } 
        helper(root);  // 2. 调用递归函数
        return max;
    }
    public int helper(TreeNode root){
        if(root==null){
            return 0;
        }
        int leftval = helper(root.left);  
        int rightval = helper(root.right);
        int currentMax = root.val;       
        if(leftval>0){
            currentMax += leftval;
        }
        if(rightval>0){
            currentMax += rightval;
        }
        if(currentMax>max){
            max = currentMax;  // 更新全局最大值
        }
        return Math.max(root.val, Math.max(leftval+root.val, rightval+root.val)); 
    }

11.在一棵二叉树中,找出相距最远的两个节点之间的距离

思路:使用后序遍历(返回值为节点的深度 ),最远距离为左子树的深度+右子树的深度+1 ,返回 左子树和右子树最高的高度;

//改进的版本
int HeightOfBinaryTree(BinaryTreeNode*pNode, int&nMaxDistance){
	if (pNode == NULL)
		return -1;   //空节点的高度为-1
	//递归
	int nHeightOfLeftTree = HeightOfBinaryTree(pNode->m_pLeft, nMaxDistance) + 1;   //左子树的的高度加1
	int nHeightOfRightTree = HeightOfBinaryTree(pNode->m_pRight, nMaxDistance) + 1;   //右子树的高度加1
	int nDistance = nHeightOfLeftTree + nHeightOfRightTree;    //距离等于左子树的高度加上右子树的高度+2
	nMaxDistance = nMaxDistance > nDistance ? nMaxDistance : nDistance;            //得到距离的最大值
	return nHeightOfLeftTree > nHeightOfRightTree ? nHeightOfLeftTree : nHeightOfRightTree;
}

12.二叉树转链表

思路:中序遍历,需要保存好前驱节点,当前节点左子树指向前驱节点,前驱节点的右子树指向当前节点;

DList BSTreeToList(BSTree tree)
{
    if(tree == NULL)
        return NULL;
    //找到最左边的结点,即转换后链表的头结点
    DLNode *head = FindLeftmostNode(tree);
    BSNode *last_node = NULL;
    //进行转换
    ConvertNode(tree, &last_node);
    return head;
}
BSNode* FindLeftmostNode(BSTree tree)
{
    if(tree == NULL)
        return NULL;
    while(tree->left != NULL)
        tree = tree->left;
    return tree;
}
void ConvertNode(BSTree tree, BSNode **last_node)
{
    if(tree == NULL)
        return;
    //对tree的左子树进行转换,last_node是转换后链表最后一个结点的指针
    if(tree->left != NULL)
        ConvertNode(tree->left, last_node);
    //调整tree的left指针,指向上一个结点
    tree->left = *last_node;
    //调整指向最后一个结点,right指向下一个结点
    if(*last_node != NULL)
        (*last_node)->right = tree;
    //调整指向最后链表一个结点的指针
    *last_node = tree;
    //对tree的右子树进行转换,last_node是转换后链表最后一个结点的指针
    if(tree->right != NULL)
        ConvertNode(tree->right, last_node);
}

扩展:链表转平衡二叉树

思路:前序遍历,方法返回当前节点,先找中间节点作为根节点然后递归的建立左子树和右子树

13.二叉树中和为某一值的路径

路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径

思路:前序遍历,将当前节点加入然后递归的处理左子树和右子树。

14.把二叉搜索树转换为累加树

给定一个二叉搜索树(Binary Search Tree),把它转换成为累加树(Greater Tree),使得每个节点的值是原来树的节点值加上所有大于它的节点值之和。

思路:使用变形的中序遍历,先处理右子树在处理左子树。

class Solution {
    int sum = 0;
    public TreeNode convertBST(TreeNode root) {
        convert(root);
        return root;
    }
    public void convert(TreeNode root) {
        if (root == null) { return ; }
        convert(root.right);
        root.val += sum;
        sum = root.val;
        convert(root.left);
    }
}

15.翻转二叉树

思路:后序遍历;

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值