[C++算法] - 二叉树《21道常见的面试题》

自己动手写代码,记录中间出现的错误。

这个网址不错https://github.com/CyC2018/CS-Notes/blob/master/notes/Leetcode%20%E9%A2%98%E8%A7%A3%20-%20%E6%A0%91.md

目录

中序遍历!

先序遍历

后序遍历

按层遍历

不分行打印( deque)

分行打印(queue)

Z字遍历(两个栈)

Moris神级遍历

 

二叉树的层高!

镜像二叉树

是否是对称二叉树?( pRoot1,  pRoot2)递归

 

是否是二叉搜索树的后序数组?

[98] 验证二叉搜索树(BST)

二叉树的左右边界打印

[96] 不同的二叉搜索树

[112] 路径总和

[113] 路径总和 II

树形dp套路

二叉树节点间的最大距离问题!

派对的最大快乐值  

最大搜索二叉子树!

是否是平衡二叉树BST

二叉树 最近公共祖先

后继节点(中序遍历)

二叉树 累积和为k的路径(先序遍历做)

二叉树 累积和为k的最长路径(先序遍历做)

重构二叉树

先序+中序

中序+后序


递归遍历是非常重要的,对于二叉树的题目来说,虽然单独让你写中序遍历的时候最好不要用递归,但是其他题目中有用到很多。inorder(Node->left,res);一直递归到最左边的null了,返回,此时压入最左边节点res.push_back(Node->val);然后看看有没有右边的节点,有的话带进去, inorder(Node->right,res);然后又回到了找最左的过程,以此循环。有一个回溯的过程。

  1. 递归遍历的过程是void函数,注意vector& res,这里的&符号。
  2. 注意Node->left的时候,要想if(node->left),这里虽然不用加,但也要有思想的过程。

中序遍历!

class Solution {
public:
    vector inorderTraversal(TreeNode* root) {     
        vector res;// 全局变量放外面,递归
        inorder(root,res);
        return res;
    }
    void inorder(TreeNode * Node,vector &res){
        if (Node==nullptr) return;// basecase
        inorder(Node->left,res);
        res.push_back(Node->val);
        inorder(Node->right,res);
    }
};

class Solution {
public:
    vector inorderTraversal(TreeNode* root) { 
        TreeNode* cur = root;
        vector res;
        stack helpStack;
        while(!helpStack.empty()||cur!=nullptr){//记住这里的条件
            if(cur!=nullptr){// while里面if就行
                helpStack.push(cur);
                cur = cur->left;
            }
            else{// 如果cur不为空!!
                cur = helpStack.top();
                helpStack.pop();
                res.push_back(cur->val);//注意是->val
                cur = cur->right;// 弹出节点的右边!!
            }
        }
        return res;
    }
};

 


先序遍历

  1. 先把head放入stack中
  2. 循环while(!stack.empty())
  3. 弹出节点并打印
  4. 将弹出节点的右节点,左节点压入

cur=stack.top();stack.pop();如果cur->right不为空stack.push_back(cur->right);如果cur->left不为空stack.push_back(cur->left);


后序遍历

利用两个栈的方式

  1. 先把head在循环外压入s1,
  2. 当s1不为空的时候,while(s1不为空)
  3. 弹出s1到s2中,
  4. 弹出节点的左右一次压入s1
  5. 最后弹出s2就是

按层遍历

  • 不分行打印( deque)

#include 
using namespace std;
void PrintFromTopToBottom(BinaryTreeNode* pRoot)
{
    if(pRoot == nullptr)
        return;

    deque dequeTreeNode;

    dequeTreeNode.push_back(pRoot);

    while(dequeTreeNode.size())//!!!!!!
    {
        BinaryTreeNode *pNode = dequeTreeNode.front();
        dequeTreeNode.pop_front();

        printf("%d ", pNode->m_nValue);

        if(pNode->m_pLeft)// !!!
            dequeTreeNode.push_back(pNode->m_pLeft);

        if(pNode->m_pRight)
            dequeTreeNode.push_back(pNode->m_pRight);
    }
}
  • 分行打印(queue)

void Print2(BinaryTreeNode* pRoot) {
	if (pRoot == nullptr)
		return;
	queueq;
	q.push(pRoot);
	int len = 0;
	while (!q.empty()) {//
		len = q.size();// 实时更新len,表示这一层的个数!!
		while (len--) {// 
			BinaryTreeNode *pCur = q.front();
			q.pop();
			cout << pCur->m_nValue<<" ";
			if (pCur->m_pLeft != nullptr) {
				q.push(pCur->m_pLeft);
			}
			if (pCur->m_pRight != nullptr) {
				q.push(pCur->m_pRight);
			}
		}
		cout << endl;	
	}
}

Z字遍历(两个栈)

void Print3(BinaryTreeNode* pRoot) {
	if (pRoot == nullptr)
		return;
	stacks1;// 存放奇数层
	stacks2;// 存放偶数层
	s1.push(pRoot);
	int len = 0;
	while (!s1.empty()||!s2.empty())
	{
		while (!s1.empty()){
			len = s1.size();
			while(len--){
				BinaryTreeNode* pCur = s1.top();
				s1.pop();
				cout << pCur->m_nValue<<" ";
				if (pCur->m_pLeft!=nullptr)
					s2.push(pCur->m_pLeft);
				if (pCur->m_pRight != nullptr)
					s2.push(pCur->m_pRight);
			}
		}
		cout << endl;
		while (!s2.empty()) {
			len = s2.size();
			while (len--) {
				BinaryTreeNode* pCur = s2.top();
				s2.pop();
				cout << pCur->m_nValue << " ";
				if (pCur->m_pRight != nullptr)
					s1.push(pCur->m_pRight);
				if (pCur->m_pLeft != nullptr)
					s1.push(pCur->m_pLeft);
			}
		}
		cout << endl;
	}

}

Moris神级遍历


镜像二叉树

将原先的二叉树变成镜像二叉树

利用先序遍历的方式,递归,开始写basecase(如果是叶子结点了,直接返回)

主体部分:交换该节点的左右节点,如果有左子节点,递归交换左子节点的左右,如果有右子节点,递归交换右子节点的左右。

void MirrorRecursively(BinaryTreeNode *pNode)
{
    if((pNode == nullptr) || (pNode->m_pLeft == nullptr && pNode->m_pRight))
        return;

    BinaryTreeNode *pTemp = pNode->m_pLeft;
    pNode->m_pLeft = pNode->m_pRight;
    pNode->m_pRight = pTemp;
    
    if(pNode->m_pLeft)
        MirrorRecursively(pNode->m_pLeft);  

    if(pNode->m_pRight)
        MirrorRecursively(pNode->m_pRight); 
}

void MirrorIteratively(BinaryTreeNode* pRoot)
{
    if(pRoot == nullptr)
        return;

    std::stack stackTreeNode;
    stackTreeNode.push(pRoot);

    while(stackTreeNode.size() > 0)
    {
        BinaryTreeNode *pNode = stackTreeNode.top();
        stackTreeNode.pop();   // 根左右,在根处交换左右

        BinaryTreeNode *pTemp = pNode->m_pLeft;
        pNode->m_pLeft = pNode->m_pRight;
        pNode->m_pRight = pTemp;

        if(pNode->m_pLeft)
            stackTreeNode.push(pNode->m_pLeft);

        if(pNode->m_pRight)
            stackTreeNode.push(pNode->m_pRight);
    }
}

是否是对称二叉树?( pRoot1,  pRoot2)递归

参数是两个(BinaryTreeNode* pRoot1, BinaryTreeNode* pRoot2)

bool isSymmetrical(BinaryTreeNode* pRoot1, BinaryTreeNode* pRoot2);

bool isSymmetrical(BinaryTreeNode* pRoot)
{
    return isSymmetrical(pRoot, pRoot);
}

bool isSymmetrical(BinaryTreeNode* pRoot1, BinaryTreeNode* pRoot2)
{
    if(pRoot1 == nullptr && pRoot2 == nullptr)//如果都相等
        return true;

    if(pRoot1 == nullptr || pRoot2 == nullptr)// 判断条件
        return false;

    if(pRoot1->m_nValue != pRoot2->m_nValue)// 判断条件
        return false;

    return isSymmetrical(pRoot1->m_pLeft, pRoot2->m_pRight)// 递归
        && isSymmetrical(pRoot1->m_pRight, pRoot2->m_pLeft);
}

是否是二叉搜索树的后序数组?

递归,参数是(int arr[],length)

后序数组,先找到根节点,然后从开头开始找比根节点小的数,然后在这之后到最后,如果还有比根节点小的数,则返回false

递归的时候看左右是不是,其实还是用先序遍历的方式~

5 7 6 9 11 10 8

bool VerifySquenceOfBST(int sequence[], int length)
{
    if(sequence == nullptr || length <= 0)
        return false;

    int root = sequence[length - 1]; //更新出根节点value

    // 在二叉搜索树中左子树的结点小于根结点
    int i = 0;
    for(; i < length - 1; ++ i)
    {
        if(sequence[i] > root)
            break;
    }

    // 在二叉搜索树中右子树的结点大于根结点
    int j = i;
    for(; j < length - 1; ++ j)
    {
        if(sequence[j] < root)
            return false;
    }

    // 判断左子树是不是二叉搜索树
    bool left = true;
    if(i > 0)
        left = VerifySquenceOfBST(sequence, i);// 递归,就是数组的范围在变换

    // 判断右子树是不是二叉搜索树
    bool right = true;
    if(i < length - 1)
        right = VerifySquenceOfBST(sequence + i, length - i - 1);

    return (left && right);//左右是不是
}

 

[98] 验证二叉搜索树(BST)

/*
 * 
 * 示例 1:
 * 
 * 输入:
 * ⁠   2
 * ⁠  / \
 * ⁠ 1   3
 * 输出: true

 */
class Solution {
    double last = LONG_MIN;//temp
public:
    bool isValidBST(TreeNode* root) {
      if (root == nullptr) {
            return true;
        }
        if (isValidBST(root->left)) {//zuo
            if (last < root->val) {//如果左节点满足,
                last = root->val;
                return isValidBST(root->right);
            }
        }
        return false;
    }
};

class Solution {
public:
    bool isValidBST(TreeNode* root) {
        // 注意 用long的最大最小值
        return check(root, LONG_MIN, LONG_MAX);
    }
    bool check(TreeNode* root, long min, long max){
        if (root == NULL)
            return true;
        if (root->val <= min || root->val >= max)
            return false;
        return check(root->left, min, root->val) && check(root->right, root->val, max);
    }

}

二叉树的层高!

用后序遍历的方式做。

站到头结点,和左树右树,要信息
int getHeight(treeNode * head,int level)
{
    //传入头结点,level为0,返回的level,就是层高
    if (head == nullptr) return level;
    left_height =  getHeight(head->left,level+1);//递归的状态,只应用于本次循环,所以每一对leftright都是一对儿。
    right_height=  getHeight(head->right,level+1);//右树高度
    return max(left_height,right_height);

}

int getHeight(treeNode * head)
{
    if (head == nullptr) return 0;
    left_height =  getHeight(head->left);//
    right_height=  getHeight(head->right);
    return max(left_height,right_height)+1;

}

// 非递归,bfs通过队列size得到本层的节点数目
int TreeDepth(TreeNode* pRoot)
    {
     queue q;
        if(!pRoot) return 0;
        q.push(pRoot);
        int level=0;
        while(!q.empty()){
            int len=q.size();
            level++;
            while(len--){
                TreeNode* tem=q.front();
                q.pop();
                if(tem->left) q.push(tem->left);
                if(tem->right) q.push(tem->right);
            }
        }
        return level;
    } 

 

二叉树的左右边界打印

辅助数据:一个二维数组

内部逻辑:中序遍历,basecase考虑某一节点,if 什么时候是左边界,if 什么时候是右边界,然后遍历左右

  • 如果是第一次遍历到这一层的edgemap[l][0]==nullptr;那么它就是左边界,edgemap[l][0]=cur
  • 如果edgemap[l][1],把最后一次遍历的cur给它就是右边界,所以不用加判断,一直edgemap[l][1]=cur,就好,循环到最后就是

凡是和层高有关系的,递归函数的形参里面都有level

  •     setEdgeMap(h->left, l + 1);
  •     setEdgeMap(h->right, l + 1);

 

  • int getHeight(treeNode * head,int level)
  • {
  •     //传入头结点,level为0,返回的level,就是层高
  •     if (head == nullptr) return level;
  •     left_height =  getHeight(head->left,level+1);//递归的状态,只应用于本次循环,所以每一对leftright都是一对儿。
  •     right_height=  getHeight(head->right,level+1);//右树高度
  •     return max(left_height,right_height);
  • }
#include "stdafx.h"
#include"iostream"
using namespace std;
struct Node {
	Node* left;
	Node* right;
	int data;
	Node(int value):data(value), left(NULL), right(NULL){}
};
Node* T;
Node* edgeMap[100 + 5][2];

void setEdgeMap(Node* h, int l)
{
	// 根左右,中序遍历,先basecase再判断,根节点的此处的逻辑,然后左右遍历
	if (h == NULL) return;
    //左边界 如果是第一次遇到就令它等于左边界
	if (edgeMap[l][0] == NULL) 
		edgeMap[l][0] = h;
    //右边界 是这一层最后一个遍历到的,所以一直赋予就可以了,到最后就是最右边界
	edgeMap[l][1] = h;
	setEdgeMap(h->left, l + 1);
	setEdgeMap(h->right, l + 1);
	
}

int main()
{
	Node* node1 = new Node(1);
	Node*node2 = new Node(2);
	Node*node3 = new Node(3);
	Node*node4 = new Node(4);
	Node*node5 = new Node(5);
	Node*node6 = new Node(6);
	Node*node7 = new Node(7);
	Node*node8 = new Node(8);
	Node*node9 = new Node(9);
	Node*node10 = new Node(10);
	Node*node11 = new Node(11);
	Node*node12 = new Node(12);
	Node*node13 = new Node(13);
	Node*node14 = new Node(14);
	Node*node15 = new Node(15);
	Node*node16 = new Node(16);
	node1->left = node2;
	node1->right = node3;
	node2->right = node4;
	node3->left = node5;
	node3->right = node6;
	node4->left = node7;
	node4->right = node8;
	node5->left = node9;
	node5->right = node10;
	node8->right = node11;
	node9->left = node12;
	node11->left = node13;
	node11->right = node14;
	node12->left = node15;
	node12->right = node16;
	setEdgeMap(node1,0);
	for (int i = 0; i < 6; i++)
	{
		cout << edgeMap[i][0]->data<<" ";

	}
	cout << endl;
	for (int i = 0; i < 6; i++)
	{
		cout << edgeMap[i][1]->data << " ";

	}
	cout << endl;

    return 0;
}

[96] 不同的二叉搜索树

/* 给定 n = 3, 一共有 5 种不同结构的二叉搜索树:
 * 
 * ⁠  1         3     3      2      1
 * ⁠   \       /     /      / \      \
 * ⁠    3     2     1      1   3      2
 * ⁠   /     /       \                 \
 * ⁠  2     1         2                 3 */
class Solution {// 总结规律,从遍历的过程。二叉搜索树的中序遍历是递增的。
public:
    int numTrees(int n) {     
        int f[n+1]={0};//注意一定要初始化!!!
        f[0]=1;
        for(int i=1;i

tricks:  

  1. 不写累加符号什么的,直接dp(n)=dp(0)*dp(n-1)+dp(1)*dp(n-2)+dp(2)*dp(n-3)+...+dp(n-1)*dp(0) ,好看。注意dp[0]=1.
  2. 注意dp(n)=dp(0)*dp(n-1)+dp(1)*dp(n-2)+dp(2)*dp(n-3)+...+dp(n-1)*dp(0) 到dp[n] + = dp[j-1]*dp[n-j] 内遍历,j从1到n。然后到dp[i] + = dp[j-1]*dp[i-j],外遍历
  3. 注意数组一定要初始化! 动态规划的题目,有几个变量就有几个for,里面一般就是if什么的。

[112] 路径总和

/*
 * 给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。

 * 
 * ⁠             5
 * ⁠            / \
 * ⁠           4   8
 * ⁠          /   / \
 * ⁠         11  13  4
 * ⁠        /  \      \
 * ⁠       7    2      1
 * 
 * 
 * 返回 true, 因为存在目标和为 22 的根节点到叶子节点的路径 5->4->11->2。
 * 
 */
class Solution {
public:
    bool hasPathSum(TreeNode* root, int sum) {
        if (root == nullptr){
            return false;
        }
        if(root->left==nullptr&&root->right==nullptr&&root->val==sum)
            return true;
        if (hasPathSum(root->left, sum-root->val))
            return true;
        if (hasPathSum(root->right, sum-root->val))
            return true;
        return false;
    }
};

[113] 路径总和 II

 

树形dp套路

  1. 二叉树节点间的最大距离问题!

  2. 派对的最大快乐值  

  3. 最大搜索二叉子树!

  4. 是否是平衡二叉树BST

  5. 二叉树 最近公共祖先


后继节点

是指在中序遍历中紧随其后的节点

搜索二叉树中,删除一个值什么的需要后继节点的函数。

分为三种情况:

        1.一个节点有右孩子,则在中序遍历中,该节点的后继是它的右子树的最左节点。

        2. 这个节点是它父亲的左孩子,则该节点的后继节点是它的父亲

        3. 这个节点是它父亲的右孩子,则需要一直向上搜索,直到它们n-1代祖先是它第n代祖先的左孩子,则它的后继就是第n个祖先。如果一直搜索到根节点,也没有找到n-1代祖先是它第n代祖先的左孩子,则该节点是整个树的中序遍历中的最后一个节点,即它没有后继。

protected Node getMinimum(Node node) {
		while (node.left != null) {
			node = node.left;
		}
		return node;
	}



	//找后继节点
	protected Node getSuccessor(Node node) {
		// if there is right branch, then successor is leftmost node of that
		// subtree
		if (node.right != null) {//如果有右子节点,找右子节点最左边的
			return getMinimum(node.right);
		} else { // otherwise it is a lowest ancestor whose left child is also
			// ancestor of node//没有右子节点找父节点
			Node currentNode = node;
			Node parentNode = node.parent;
			while (parentNode != null && currentNode == parentNode.right) {
				// go up until we find parent that currentNode is not in right
				// subtree.
				currentNode = parentNode;
				parentNode = parentNode.parent;
			}
			return parentNode;
		}
	}

二叉树 累积和为k的路径(先序遍历做

注意这里的路径,强调了必须是从根节点到叶节点的路径才算。

先序遍历做(在根节点判断满不满足,然后左右遍历),递归,形参(root,vector,k,cursum)

每次更新的时候,

  1. 根节点处:需要更新cursum和vector,判断cursum是不是等于k && 当前的节点是不是叶子节点,满足条件遍历vector,打印。
  2. 遍历左右子节点:如果左子节点不为空,遍历递归左边,如果右子节点不为空,遍历递归右边。
  3. 在末尾,返回父函数的时候,吧vector和cursum减去当前的值。
void FindPath(BinaryTreeNode* pRoot, int expectedSum, std::vector& path, int& currentSum);

void FindPath(BinaryTreeNode* pRoot, int expectedSum)
{
    if(pRoot == nullptr)
        return;

    std::vector path;
    int currentSum = 0;
    FindPath(pRoot, expectedSum, path, currentSum);
}

void FindPath
(
    BinaryTreeNode*   pRoot,        
    int               expectedSum,  
    std::vector& path,         
    int&              currentSum
)
{   //更新参数
    currentSum += pRoot->m_nValue;
    path.push_back(pRoot->m_nValue);

    // 如果是叶结点,并且路径上结点的和等于输入的值
    // 打印出这条路径
    bool isLeaf = pRoot->m_pLeft == nullptr && pRoot->m_pRight == nullptr;
    if(currentSum == expectedSum && isLeaf)
    {
        printf("A path is found: ");
        std::vector::iterator iter = path.begin();
        for(; iter != path.end(); ++ iter)
            printf("%d\t", *iter);
        
        printf("\n");
    }

    // 如果有相应的节点,则遍历它的子结点
    if(pRoot->m_pLeft != nullptr)
        FindPath(pRoot->m_pLeft, expectedSum, path, currentSum);
    if(pRoot->m_pRight != nullptr)
        FindPath(pRoot->m_pRight, expectedSum, path, currentSum);

    // 在返回到父结点之前,在路径上删除当前结点,
    // 并在currentSum中减去当前结点的值
    currentSum -= pRoot->m_nValue;
    path.pop_back();
} 

二叉树 累积和为k的最长路径

这道题需要数组里累积和为k的最长路径作为基础,同样是利用map,来做,不同的是,这里开始时map[0]=0,表示什么都不加的时候0层。先序遍历做,和上一题差不多,在根节点处判断,然后遍历左右子节点,返回父节点的时候需要特别注意一下。

需要的参数 root,cursum,k,map,level,maxlen

int getMaxlen(BinaryTreeNode *root, int k) {
	if (root == nullptr)return 0;
	mapm;
	m[0] = 0;
	getMaxlen(root, k, 0, 1, 0, m);// 当前第一层
}

int getMaxlen(BinaryTreeNode *root, int k, int cursum, int level, int maxlen, mapm) {
	cursum += root->m_nValue;
	if (!m.count(cursum)) {
		m[cursum] = level;
	}
	if (m.count(cursum - k)) {
		maxlen = max(maxlen, level - m[cursum - k]);
	}
	maxlen = getMaxlen(root->m_pLeft, k, cursum, level + 1, maxlen, m);
	maxlen = getMaxlen(root->m_pRight, k, cursum, level + 1, maxlen, m);
	if ( m[cursum]==level ) {
		m.erase(cursum);
	}
	return maxlen;
}

重构二叉树

  1. 先序+中序

  2. 中序+后序

其他的情况我就不写了,值得说明的是先序+后序重构二叉树有些是重构不出来的。而上面的两个,基本思路一致,我就只说明第一个了。注意这里的二叉树都不含重复的数字!

先序 根左右   1 2 4 7 3 5 6 8

中序 左根右   4 7 2 1 5 3 8 6

首先从先序中找到根,然后在中序中找到根的位置,以此判断左子树和右子树。不断递归。每次递归返回的都是当前状态的根节点。

函数的参数有(先序开始,先序的结束,中序的开始,中序的结束)也是那种常见的数组的开头和结尾,不断缩短的形式

作用是:返回当前的根节点,并利用确定的左右子树的范围,构建左右子树(利用递归函数)

// 需要先声明
BinaryTreeNode* ConstructCore(int* startPreorder, int* endPreorder, int* startInorder, int* endInorder);

BinaryTreeNode* Construct(int* preorder, int* inorder, int length)
{
    if(preorder == nullptr || inorder == nullptr || length <= 0)
        return nullptr;

    return ConstructCore(preorder, preorder + length - 1,
        inorder, inorder + length - 1);
}

BinaryTreeNode* ConstructCore//注意这里的参数,也是那种常见的数组的开头和结尾,不断缩短的形式
(
    int* startPreorder, int* endPreorder, 
    int* startInorder, int* endInorder
)
{
    // 前序遍历序列的第一个数字是根结点的值
    int rootValue = startPreorder[0];
    BinaryTreeNode* root = new BinaryTreeNode();// 注意这里构建的形式
    root->m_nValue = rootValue;// 初始化
    root->m_pLeft = root->m_pRight = nullptr;
    // basecase 先序和中序数组都是剩下一个值,这个值必须还是相等的
    if(startPreorder == endPreorder)
    {   
        if(startInorder == endInorder && *startPreorder == *startInorder)
            return root;
        else
            throw std::exception("Invalid input.");
    }

    // 在中序遍历中找到根结点的值
    int* rootInorder = startInorder;
    while(rootInorder <= endInorder && *rootInorder != rootValue)
        ++ rootInorder;

    if(rootInorder == endInorder && *rootInorder != rootValue)
        throw std::exception("Invalid input.");

    int leftLength = rootInorder - startInorder;
    int* leftPreorderEnd = startPreorder + leftLength;
    if(leftLength > 0)// 如果有左子树
    {
        // 构建左子树,注意这里函数返回的是左子树的根节点
        root->m_pLeft = ConstructCore(startPreorder + 1, leftPreorderEnd, 
            startInorder, rootInorder - 1);
    }// 如果有右子树
    if(leftLength < endPreorder - startPreorder)
    {
        // 构建右子树
        root->m_pRight = ConstructCore(leftPreorderEnd + 1, endPreorder,
            rootInorder + 1, endInorder);
    }

    return root;
}

 

你可能感兴趣的:(CV面经,算法,数据结构,CV面经+算法总结)