LeetCode654. 最大二叉樹

Tomorrowland_D發表於2024-07-28

題目連結: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的時候,構造了一個新的節點,並返回。

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

3.遞迴的單層邏輯:

我們需要找出這個陣列中的最大值,然後對這個陣列進行分割,最大值左邊的陣列來構造左子樹,最大值右邊的陣列來構造右子樹,不過在此之前,我們還得找到最大值和最大值所對應的下標

		//找到了這個陣列中的最大的元素和最大元素所在的下標
		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的左子樹和右子樹進行構造的過程,我們可以使用兩個陣列,來儲存最大值左邊的序列和最大值右邊的序列

		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;

這幾步做完以後,基本就完成了


//最大二叉樹
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;
	}
};

進階

我們可以不適用額外的陣列空間,我們可以直接對傳入的陣列的下標進行操作

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限制一下, 終止條件也會相應的調整。

相關文章