題目連結: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 的節點。
- 空陣列,無子節點。
- [3,2,1] 中的最大值是 3 ,左邊部分是 [] ,右邊部分是 [2,1] 。
示例 2:
輸入:nums = [3,2,1]
輸出:[3,null,2,null,1]
提示:
1 <= nums.length <= 1000
0 <= nums[i] <= 1000
nums 中的所有整數 互不相同
思路:
構造樹一般採用的是前序遍歷,因為先構造中間節點,然後遞迴構造左子樹和右子樹。
我們來走一下遞迴的三步法:
-
遞迴函式的引數和返回值:返回值明顯為TreeNode的節點型別,引數我們需要傳入一個陣列
-
遞迴結束的條件:題目中說了輸入的陣列大小一定是大於等於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限制一下, 終止條件也會相應的調整。