LeetCode刷题总结:(5)二叉树和递归的相关问题

一般关于二叉树的问题第一反应就是能不能用递归的方法做,因为二叉树的定义本身就是完美的递归,多以关于二叉树的问题一般都是先考虑能不能抽象出一个递归的结构这样去做。

 

104. 二叉树的最大深度

给定一个二叉树,找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

说明: 叶子节点是指没有子节点的节点。

示例:
给定二叉树 [3,9,20,null,null,15,7]

    3
   / \
  9  20
    /  \
   15   7

返回它的最大深度 3 。

// 思路: 递归思想,二叉树的最大深度就是根节点的最大深度,也就是左右子树中最大深度加上1,以上定义完美的符合递归的定义
class Solution {
public:
	int maxDepth(TreeNode* root) {

		if (root == NULL)
			return 0;

		return max(maxDepth(root->left), maxDepth(root->right)) + 1;
	}
};

 

111. 二叉树的最小深度

给定一个二叉树,找出其最小深度。

最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

说明: 叶子节点是指没有子节点的节点。

示例:

给定二叉树 [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7

返回它的最小深度  2.

// 思路: 还是沿用上面的递归,但是不是简单的将max变成min,因为最小深度的定义是首先是要有深度的,在有深度里取较小的深度,所以首先要判断当前节点的左右子节点的存在情况
class Solution1 {
public:
	int minDepth(TreeNode* root) {

		if (root == NULL)
			return 0;

		// 如果当前节点具有左右子节点
		if (root->left != NULL&&root->right != NULL) {
			return min(minDepth(root->left), minDepth(root->right)) + 1;
		}
		else if (root->left != NULL) {		// 如果只有其中一个
			return minDepth(root->left) + 1;
		}
		else if (root->right != NULL) {
			return minDepth(root->right) + 1;
		}
		else {	// 没有左右子节点
			return 1;
		}
		
	}
};

 

226. 翻转二叉树

翻转一棵二叉树。

示例:

输入:

     4
   /   \
  2     7
 / \   / \
1   3 6   9

输出:

     4
   /   \
  7     2
 / \   / \
9   6 3   1
// 思路: 递归,翻转二叉树,翻转根节点的左右子树,翻转当前节点的左右子树
class Solution2 {
public:
	TreeNode* invertTree(TreeNode* root) {

		if (root == NULL)
			return NULL;

		// 左右子树进行翻转
		root->left = invertTree(root->left);
		root->right = invertTree(root->right);
		// 当前节点翻转
		swap(root->left, root->right);

		return root;
	}
};

 

100. 相同的树

给定两个二叉树,编写一个函数来检验它们是否相同。

如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。

示例 1:

输入:       1         1
          / \       / \
         2   3     2   3

        [1,2,3],   [1,2,3]

输出: true

示例 2:

输入:      1          1
          /           \
         2             2

        [1,2],     [1,null,2]

输出: false

示例 3:

输入:       1         1
          / \       / \
         2   1     1   2

        [1,2,1],   [1,1,2]

输出: false
// 当前节点值一致,左右子树也是相同的树,
class Solution3 {
public:
	bool isSameTree(TreeNode* p, TreeNode* q) {
		
		if (p == NULL && q == NULL)
			return true;
		else if (p != NULL && q != NULL) {	// 递归情况
			if (p->val == q->val && isSameTree(p->left, q->left) && isSameTree(p->right, q->right))
				return true;
			else
				return false;
		}
		else 
			return false;
		
	}
};

 

101. 对称二叉树

给定一个二叉树,检查它是否是镜像对称的。

例如,二叉树 [1,2,2,3,4,4,3] 是对称的。

    1
   / \
  2   2
 / \ / \
3  4 4  3

但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:

    1
   / \
  2   2
   \   \
   3    3

说明:

如果你可以运用递归和迭代两种方法解决这个问题,会很加分。

class Solution4 {
public:
	bool isSymmetric(TreeNode* root) {

		// 终止条件
		if (root == NULL)
			return true;

		return isSymmetric(root->left, root->right);
		
	}

	// 判断以p,q为根节点的两颗树是否对称
	static bool isSymmetric(TreeNode* p, TreeNode* q) {

		if (p == NULL && q == NULL)
			return true;
		if (p != NULL && q != NULL) {
			if (p->val == q->val) {
				if (isSymmetric(p->left, q->right) && isSymmetric(p->right, q->left))		// 这里这么递归对比就能很好的符合对称关系
					return true;
				else
					return false;
			}
			else return false;
		}
		else
			return false;
		
	}
};

 

222. 完全二叉树的节点个数

给出一个完全二叉树,求出该树的节点个数。

说明:

完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2h 个节点。

示例:

输入: 
    1
   / \
  2   3
 / \  /
4  5 6

输出: 6
class Solution5 {
public:
	int countNodes(TreeNode* root) {

		// 这样超时了!!!
		/*if (root == NULL)
			return 0;

		return countNodes(root->left) + countNodes(root->right) + 1;*/




		// 思路2:求完全二叉树的结点个数。可以求左右子树的高度,
		//1.如果相等则说明左子树是满二叉树,那么可以根据左子树的高度求出左子树加上根节点的结点数为2^leftHeight,再加上右子树的结点即为以当前结点为根的结点总数
		//	2.否则说明右子树为满二叉树,同样右子树的结点加上根节点的结点数为2^rightHeight,再加上左子树结点即为当前结点为根的结点总数
		int res = 0;

		while (root != NULL) {

			int leftDepth = depth(root->left);
			int rightDepth = depth(root->right);
			if (leftDepth == rightDepth) {	// 说明左子树是完全二叉树
				res += pow(2, leftDepth);
				root = root->right;		// 变成了一个结构相同的子问题
			}
			else {
				res += pow(2, rightDepth);
				root = root->left;
			}
		}

		return res;
	}

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

 

110. 平衡二叉树

给定一个二叉树,判断它是否是高度平衡的二叉树。

本题中,一棵高度平衡二叉树定义为:

一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。

示例 1:

给定二叉树 [3,9,20,null,null,15,7]

    3
   / \
  9  20
    /  \
   15   7

返回 true 。

示例 2:

给定二叉树 [1,2,2,3,3,null,null,4,4]

       1
      / \
     2   2
    / \
   3   3
  / \
 4   4

返回 false 。

// 思路: 判断树是否平衡,就判断根节点的两颗子树高度差是否在1之内,当然两颗子树要求也是平衡树才行,问题就转化成了如何去求解一根节点的深度
class Solution6 {
public:
	bool isBalanced(TreeNode* root) {

		if (root == NULL)
			return true;

		int t = depth(root->left) - depth(root->right);
		if (t >= -1 && t <= 1 && isBalanced(root->left) && isBalanced(root->right))
			return true;
		else return false;
	}

	static int depth(TreeNode* node) {
		if (node == NULL)
			return 0;

		return max(depth(node->left) + 1, depth(node->right) + 1);
	}
	
};

 

112. 路径总和

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

说明: 叶子节点是指没有子节点的节点。

示例: 
给定如下二叉树,以及目标和 sum = 22

              5
             / \
            4   8
           /   / \
          11  13  4
         /  \      \
        7    2      1

返回 true, 因为存在目标和为 22 的根节点到叶子节点的路径 5->4->11->2

// 思路: 判断到达叶子结点时,叶子结点的val是否就是剩余的sum值,是就true,否则就false,整个过程递归的检测左右子树是否有符合条件的路径
class Solution7 {
public:
	bool hasPathSum(TreeNode* root, int sum) {

		if (root == NULL)
			return false;

		if (root->left == NULL && root->right == NULL) {
			if (root->val == sum)
				return true;
			else
				return false;
		}

		return (hasPathSum(root->left, sum - root->val) || hasPathSum(root->right, sum - root->val));
	}
};

 

404. 左叶子之和

计算给定二叉树的所有左叶子之和。

示例:

    3
   / \
  9  20
    /  \
   15   7

在这个二叉树中,有两个左叶子,分别是 9 和 15,所以返回 24
class Solution8 {
public:
	int sumOfLeftLeaves(TreeNode* root) {

		if (root == NULL)
			return 0;

		int res = 0;

		if (root->left != NULL)		// 当前节点有左子节点
			if (root->left->left == NULL && root->left->right == NULL)	// 当前节点的左子节点是叶子结点
				res += root->left->val;
			
		res += sumOfLeftLeaves(root->left);
		// 继续在右子树中搜
		res += sumOfLeftLeaves(root->right);
		
		return res;
	}
};

 

257. 二叉树的所有路径

给定一个二叉树,返回所有从根节点到叶子节点的路径。

说明: 叶子节点是指没有子节点的节点。

示例:

输入:

   1
 /   \
2     3
 \
  5

输出: ["1->2->5", "1->3"]

解释: 所有根节点到叶子节点的路径为: 1->2->5, 1->3
class Solution9 {
public:
	vector binaryTreePaths(TreeNode* root) {

		if (root == NULL)
			return vector();

		vector res;

		// 找到了叶子结点,意味着要生成一条路径了
		if (root->left == NULL && root->right == NULL) {
			res.push_back(to_string(root->val));
			return res;
		}

		// 左子树的路径
		vector leftS = binaryTreePaths(root->left);
		for (int i = 0; i < leftS.size(); i++) {
			res.push_back(to_string(root->val) + "->" + leftS[i]);
		}
		// 右子树的路径
		vector rightS = binaryTreePaths(root->right);
		for (int i = 0; i < rightS.size(); i++) {
			res.push_back(to_string(root->val) + "->" + rightS[i]);
		}

		return res;
	}
};

 

113. 路径总和 II

给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。

说明: 叶子节点是指没有子节点的节点。

示例:
给定如下二叉树,以及目标和 sum = 22

              5
             / \
            4   8
           /   / \
          11  13  4
         /  \    / \
        7    2  5   1

返回:

[
   [5,4,11,2],
   [5,8,4,5]
]
// 思路: 是solution9和solution7的结合,终止的边界条件是sum,路径的记录方法是solution9的方式
class Solution10 {
public:
	vector> pathSum(TreeNode* root, int sum) {

		vector> res_tmp = _pathSum(root, sum);
		vector> res= res_tmp;

		// 形式整理输出,本来是倒序的
		for (int i = 0; i < res_tmp.size(); i++) {
			int len = res_tmp[i].size();
			for (int j = 0; j < len; j++) {
				res[i][j] = res_tmp[i][len - 1 - j];
			}
		}

		return res;
	}

	static vector> _pathSum(TreeNode* root, int sum) {
		
		if (root == NULL)
			return vector>();

		vector> res;

		// 找到了符合题意的叶子结点,意味着生成了一条路径
		if (root->left == NULL && root->right == NULL && root->val == sum) {
			vector path;
			path.push_back(root->val);
			res.push_back(path);
		}

		// 当前节点的左子树的正确路径
		vector> leftPath = _pathSum(root->left, sum - root->val);
		for (int i = 0; i < leftPath.size(); i++) {
			leftPath[i].push_back(root->val);
		}
		// 当前节点的右子树的正确路径
		vector> rightPath = _pathSum(root->right, sum - root->val);
		for (int i = 0; i < rightPath.size(); i++) {
			rightPath[i].push_back(root->val);
		}
		// 整理成当前节点正确路径数的形式
		for (int i = 0; i < leftPath.size(); i++) {
			res.push_back(leftPath[i]);
		}
		for (int i = 0; i < rightPath.size(); i++) {
			res.push_back(rightPath[i]);
		}

		return res;
	}
};

 

129. 求根到叶子节点数字之和

给定一个二叉树,它的每个结点都存放一个 0-9 的数字,每条从根到叶子节点的路径都代表一个数字。

例如,从根到叶子节点路径 1->2->3 代表数字 123

计算从根到叶子节点生成的所有数字之和。

说明: 叶子节点是指没有子节点的节点。

示例 1:

输入: [1,2,3]
    1
   / \
  2   3
输出: 25
解释:
从根到叶子节点路径 1->2 代表数字 12.
从根到叶子节点路径 1->3 代表数字 13.
因此,数字总和 = 12 + 13 = 25.

示例 2:

输入: [4,9,0,5,1]
    4
   / \
  9   0
 / \
5   1
输出: 1026
解释:
从根到叶子节点路径 4->9->5 代表数字 495.
从根到叶子节点路径 4->9->1 代表数字 491.
从根到叶子节点路径 4->0 代表数字 40.
因此,数字总和 = 495 + 491 + 40 = 1026
//  思路: 是solution9的变异,只需将所有从根节点到叶子结点之间的路径的存储方式和最终的显示方式做了相应的改变
class Solution11 {
public:
	int sumNumbers(TreeNode* root) {

		vector> resVec = _sumNumbers(root);
		int res = 0;

		// 整理成输出的形式
		for (int i = 0; i < resVec.size(); i++) {
			for (int j = 0; j < resVec[i].size(); j++) {
				res += resVec[i][j] * pow(10, j);
			}
		}

		return res;
	}

	vector> _sumNumbers(TreeNode* root) {

		if (root == NULL)
			return vector>();

		vector> res;

		// 找到叶子结点,就是找到了路径上的终点
		if (root->left == NULL && root->right == NULL) {
			vector path;
			path.push_back(root->val);
			res.push_back(path);
		}

		vector> leftPath = _sumNumbers(root->left);
		for (int i = 0; i < leftPath.size(); i++) {
			vector tmp = leftPath[i];
			tmp.push_back(root->val);
			res.push_back(tmp);
		}

		vector> rightPath = _sumNumbers(root->right);
		for (int i = 0; i < rightPath.size(); i++) {
			vector tmp = rightPath[i];
			tmp.push_back(root->val);
			res.push_back(tmp);
		}

		return res;
	}
};

 

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

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

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

例如,给定如下二叉搜索树:  root = [6,2,8,0,4,7,9,null,null,3,5]

        _______6______
       /              \
    ___2__          ___8__
   /      \        /      \
   0      _4       7       9
         /  \
         3   5

示例 1:

输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
输出: 6 
解释: 节点 2 和节点 8 的最近公共祖先是 6。

示例 2:

输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
输出: 2
解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。

说明:

  • 所有节点的值都是唯一的。
  • p、q 为不同节点且均存在于给定的二叉搜索树中。
// 思路: 其实就是寻找递归结构的问题
// 1. 如果p和q在左右子树中,那么root就是最近公共祖先节点;
// 2. 如果p,q其中有一个是当前的root,那么root就是最近公共祖先节点;
// 3. 如果p和q都在当前root的一侧,那么root可以递归变为root->相应侧子节点,形成了递归结构
class Solution12 {
public:
	TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {

		if (root == NULL)
			return NULL;

		if (p->val < root->val && q->val < root->val)
			return lowestCommonAncestor(root->left, p, q);
		if (p->val > root->val && q->val > root->val)
			return lowestCommonAncestor(root->right, p, q);

		return root;
	}
};

 

98. 验证二叉搜索树

给定一个二叉树,判断其是否是一个有效的二叉搜索树。

假设一个二叉搜索树具有如下特征:

  • 节点的左子树只包含小于当前节点的数。
  • 节点的右子树只包含大于当前节点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。

示例 1:

输入:
    2
   / \
  1   3
输出: true

示例 2:

输入:
    5
   / \
  1   4
     / \
    3   6
输出: false
解释: 输入为: [5,1,4,null,null,3,6]。
     根节点的值为 5 ,但是其右子节点值为 4 。
// 思路: 其实二分查找树的中序遍历就是将树中的值按从小到大遍历了,如果不符合从小到大,那么就不是二叉搜索树
class Solution13 {
public:
	bool isValidBST(TreeNode* root) {

		if (root == NULL)
			return false;

		vector resVec = inOrder(root);

		for (int i = 1; i < resVec.size(); i++) {
			if (resVec[i] <= resVec[i - 1])
				return false;
		}

		return true;
	}

	static vector inOrder(TreeNode* root) {

		if (root == NULL)
			return vector();

		vector res;

		vector resLeft = inOrder(root->left);
		for (int i = 0; i < resLeft.size(); i++)
			res.push_back(resLeft[i]);

		res.push_back(root->val);

		vector resRight = inOrder(root->right);
		for (int i = 0; i < resRight.size(); i++)
			res.push_back(resRight[i]);

		return res;
	}
};

 

108. 将有序数组转换为二叉搜索树

将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。

本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。

示例:

给定有序数组: [-10,-3,0,5,9],

一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树:

      0
     / \
   -3   9
   /   /
 -10  5
// 思路: 找出递归结构如下,nums中间位的数值赋予当前节点的值,nums两侧的值递归地赋予为当前节点两侧的子树
class Solution14 {
public:
	TreeNode* sortedArrayToBST(vector& nums) {

		TreeNode* res = arrayToBST(nums, 0, nums.size()-1);
		return res;
	}

	TreeNode* arrayToBST(vector& nums, int begin, int end) {

		if (begin > end)
			return NULL;

		int mid = begin + (end - begin) / 2;

		TreeNode* res = new TreeNode(nums[mid]);

		res->left = arrayToBST(nums, begin, mid - 1);

		res->right = arrayToBST(nums, mid + 1, end);

		return res;			
	}

};

 

230. 二叉搜索树中第K小的元素

给定一个二叉搜索树,编写一个函数 kthSmallest 来查找其中第 个最小的元素。

说明:
你可以假设 k 总是有效的,1 ≤ k ≤ 二叉搜索树元素个数。

示例 1:

输入: root = [3,1,4,null,2], k = 1
   3
  / \
 1   4
  \
   2
输出: 1

示例 2:

输入: root = [5,3,6,2,4,null,null,1], k = 3
       5
      / \
     3   6
    / \
   2   4
  /
 1
输出: 3

进阶:
如果二叉搜索树经常被修改(插入/删除操作)并且你需要频繁地查找第 k 小的值,你将如何优化 kthSmallest 函数?

// 思路: 获取中序遍历的vector,直接得到第k个数即可。其实没必要获取得到所有的中序遍历结果之后再停止,可以直接记数到了k停止即可,这样后面的结果就不用统计了
class Solution15 {
public:
	int kthSmallest(TreeNode* root, int k) {

		vector res;
		_inOrder(root, k, res);

		return res[k - 1];
	}

	void _inOrder(TreeNode* root, int k, vector& res) {

		if (root == NULL)
			return;

		_inOrder(root->left, k, res);
		res.push_back(root->val);
		if (res.size() >= k)
			return;
		_inOrder(root->right, k, res);

		return;	
	}


};

 

你可能感兴趣的:(LeetCode刷题记录)