LeetCode654. 最大二叉树

题目链接:https://leetcode.cn/problems/maximum-binary-tree/description/

题目叙述

给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建:

创建一个根节点,其值为 nums 中的最大值。

递归地在最大值 左边 的 子数组前缀上 构建左子树。

递归地在最大值 右边 的 子数组后缀上 构建右子树。

返回 nums 构建的 最大二叉树 。

示例 1:

输入:nums = [3,2,1,6,0,5]

输出:[6,3,5,null,2,0,null,null,1]

解释:递归调用如下所示:

  • [3,2,1,6,0,5] 中的最大值是 6 ,左边部分是 [3,2,1] ,右边部分是 [0,5] 。
    • [3,2,1] 中的最大值是 3 ,左边部分是 [] ,右边部分是 [2,1] 。
      • 空数组,无子节点。
      • [2,1] 中的最大值是 2 ,左边部分是 [] ,右边部分是 [1] 。
        • 空数组,无子节点。
        • 只有一个元素,所以子节点是一个值为 1 的节点。
    • [0,5] 中的最大值是 5 ,左边部分是 [0] ,右边部分是 [] 。
      • 只有一个元素,所以子节点是一个值为 0 的节点。
      • 空数组,无子节点。

示例 2:

输入:nums = [3,2,1]

输出:[3,null,2,null,1]

提示:

1 <= nums.length <= 1000

0 <= nums[i] <= 1000

nums 中的所有整数 互不相同

思路:

构造树一般采用的是前序遍历,因为先构造中间节点,然后递归构造左子树和右子树。

我们来走一下递归的三步法:

  1. 递归函数的参数和返回值:返回值明显为TreeNode的节点类型,参数我们需要传入一个数组

  2. 递归结束的条件:题目中说了输入的数组大小一定是大于等于1的,所以我们不用考虑小于1的情况,那么当递归遍历的时候,如果传入的数组大小为1,说明遍历到了叶子节点了。

    那么应该定义一个新的节点,并把这个数组的数值赋给新的节点,然后返回这个节点。 这表示一个数组大小是1的时候,构造了一个新的节点,并返回。

cpp 复制代码
		TreeNode* node = new TreeNode(0);
		if (nums.size() == 1) {
			node->val = nums[0];
			return node;
		}

3.递归的单层逻辑:

我们需要找出这个数组中的最大值,然后对这个数组进行分割,最大值左边的数组来构造左子树,最大值右边的数组来构造右子树,不过在此之前,我们还得找到最大值和最大值所对应的下标

cpp 复制代码
		//找到了这个数组中的最大的元素和最大元素所在的下标
		int maxValue = 0;
		int index = 0;
		for (int i = 0; i < nums.size(); i++) {
			if (nums[i] > maxValue) {
				index = i;
				maxValue = nums[i];
			}
		}
		//对根节点进行赋值
		node->val = maxValue;

然后就是对根节点node的左子树和右子树进行构造的过程,我们可以使用两个数组,来存储最大值左边的序列和最大值右边的序列

cpp 复制代码
		if (index >= 1) {
            //因为vector的拷贝构造函数是左开右闭的逻辑
			vector<int> newVec(nums.begin(), nums.begin() + index);
			node->left = constructMaximumBinaryTree(newVec);
		}
		//确保右边子树的元素个数≥1
		if ((nums.size() - 1) - index > 0) {
			vector<int> newVec(nums.begin() + index + 1, nums.end());
			node->right = constructMaximumBinaryTree(newVec);
		}
		return node;

这几步做完以后,基本就完成了

cpp 复制代码
//最大二叉树
class Solution {
public:
	TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
		TreeNode* node = new TreeNode(0);
		if (nums.size() == 1) {
			node->val = nums[0];
			return node;
		}
		//找到了这个数组中的最大的元素和最大元素所在的下标
		int maxValue = 0;
		int index = 0;
		for (int i = 0; i < nums.size(); i++) {
			if (nums[i] > maxValue) {
				index = i;
				maxValue = nums[i];
			}
		}
		//对根节点进行赋值
		node->val = maxValue;
		//对左子树进行构造(确保左边数组的元素个数≥1)
		if (index >= 1) {
			vector<int> newVec(nums.begin(), nums.begin() + index);
			node->left = constructMaximumBinaryTree(newVec);
		}
		//确保右边子树的元素个数≥1
		if ((nums.size() - 1) - index > 0) {
			vector<int> newVec(nums.begin() + index + 1, nums.end());
			node->right = constructMaximumBinaryTree(newVec);
		}
		return node;
	}
};

进阶

我们可以不适用额外的数组空间,我们可以直接对传入的数组的下标进行操作

cpp 复制代码
class Solution {
public:
    TreeNode* traversal(vector<int> &nums,int left,int right){
        //当左区间≥右区间,就返回
        if(left>=right) return nullptr;
        //记录最大值的下标
        int maxValueIndex=left;
        for(int i=left+1;i<right;i++){
            if(nums[i]>nums[maxValueIndex]) maxValueIndex=i;
        }
        //构造根节点
        TreeNode* node=new TreeNode(nums[maxValueIndex]);
        //构造左子树和右子树
        node->left=traversal(nums,left,maxValueIndex);
        node->right=traversal(nums,maxValueIndex+1,right);
        //返回根节点
        return node;
    }
    TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
        return traversal(nums,0,nums.size());
    }
};

总结

注意类似用数组构造二叉树的题目,每次分隔尽量不要定义新的数组,而是通过下标索引直接在原数组上操作,这样可以节约时间和空间上的开销。

什么时候递归函数前面加if,什么时候不加if?

其实就是不同代码风格的实现,一般情况来说:如果让空节点(空指针)进入递归,就不加if,如果不让空节点进入递归,就加if限制一下, 终止条件也会相应的调整。