108. 将有序数组转换为二叉搜索树
一、题目
给你一个整数数组 nums
,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。
高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。
示例 1:
输入:nums = [-10,-3,0,5,9]
输出:[0,-3,9,-10,null,5]
解释:[0,-10,5,null,-3,null,9] 也将被视为正确答案:
示例 2:
输入:nums = [1,3]
输出:[3,1]
解释:[1,null,3] 和 [3,1] 都是高度平衡二叉搜索树。
提示:
1 <= nums.length <= 104
-104 <= nums[i] <= 104
nums
按 严格递增 顺序排列
二、题解
方法一:递归
我们知道平衡二叉搜索树的特点是每个节点的左子树和右子树的高度差不超过1,因此我们尽可能要让节点两端的元素个数相近。
算法思路:
-
首先,我们要找到一个合适的根节点。由于给定的数组是有序的,我们可以选择中间位置的元素作为根节点,这样可以保证左右子树的元素数量相差不大,从而有助于维持平衡性质。
-
在选择了根节点之后,我们将数组分成两部分:左子数组和右子数组。左子数组中的元素都小于根节点,右子数组中的元素都大于根节点。
-
然后,我们递归地对左子数组和右子数组进行相同的操作,分别构建左子树和右子树。这样就能保证左右子树的高度差不超过1。
-
最后,将根节点与构建好的左右子树连接起来,形成一棵完整的平衡二叉搜索树。
具体实现:
class Solution {
public:
TreeNode* sortedArrayToBST(vector<int>& nums) {
if(nums.size() == 0) return nullptr; // 空数组直接返回空指针(终止条件)
int size = nums.size();
int middle = size/2; // 找到中间位置的索引
TreeNode *root = new TreeNode(nums[middle]); // 创建根节点
vector<int> vecL(nums.begin(),nums.begin()+middle); // 左子数组
vector<int> vecR(nums.begin()+middle+1, nums.end()); // 右子数组
root->left = sortedArrayToBST(vecL); // 递归构建左子树
root->right = sortedArrayToBST(vecR); // 递归构建右子树
return root; // 返回根节点
}
};
算法分析:
- 时间复杂度:每个元素都会被遍历一次,因此总的时间复杂度为 O(n),其中 n 是数组中的元素数量。
- 空间复杂度:每次递归都会创建新的左右子数组,因此递归的最大深度是 log(n),每层需要的空间为 O(n),所以总的空间复杂度为 O(nlogn)。
方法二:迭代
以下是代码的思路:
-
首先,检查给定的有序数组是否为空,如果为空,则直接返回空指针,表示空树。
-
创建一个根节点
root
,暂时将其值设为0,作为占位值。然后创建三个队列:nodeQue
用于存储待处理的节点,leftQue
用于存储每个节点对应的左区间下标,rightQue
用于存储每个节点对应的右区间下标。 -
将根节点
root
、左区间下标0和右区间下标nums.size() - 1
分别入队列。 -
进入迭代循环,从队列中取出一个待处理的节点
curNode
,同时获取它对应的左区间下标left
和右区间下标right
,计算当前区间的中间位置mid
。 -
将中间位置
mid
对应的元素值赋给当前节点curNode->val
,此时树的结构仍未构建。 -
如果左区间
left
小于等于mid - 1
,则创建左子节点,并将其入队列。更新leftQue
和rightQue
分别为left
和mid - 1
。 -
如果右区间
right
大于等于mid + 1
,则创建右子节点,并将其入队列。更新leftQue
为mid + 1
,并维持rightQue
不变。 -
重复以上步骤,直到队列为空,此时所有的节点都已经处理完毕,构建的二叉搜索树也完成。
-
返回根节点
root
,即完成了整个构建过程。
class Solution {
public:
TreeNode* sortedArrayToBST(vector<int>& nums) {
if (nums.empty()) {
return nullptr;
}
TreeNode* root = new TreeNode(0); // 初始根节点,暂时使用0作为占位值
queue<TreeNode*> nodeQue; // 存储待处理的节点
queue<int> leftQue; // 存储当前节点对应的左区间下标
queue<int> rightQue; // 存储当前节点对应的右区间下标
nodeQue.push(root); // 根节点入队列
leftQue.push(0); // 0为初始左区间下标
rightQue.push(nums.size() - 1); // nums.size() - 1为初始右区间下标
while (!nodeQue.empty()) {
TreeNode* curNode = nodeQue.front(); nodeQue.pop();// 当前待处理节点
int left = leftQue.front(); leftQue.pop(); // 当前节点对应的左区间下标
int right = rightQue.front(); rightQue.pop(); // 当前节点对应的右区间下标
int mid = left + (right - left) / 2; // 计算当前区间的中间位置
curNode->val = nums[mid]; // 将中间位置元素赋值给当前节点,此时树的结构仍未构建
if (left <= mid - 1) { // 如果左区间仍有元素可用,创建左子节点
curNode->left = new TreeNode(0);
nodeQue.push(curNode->left); // 将左子节点加入待处理队列
leftQue.push(left); // 更新左区间下标
rightQue.push(mid - 1); // 更新右区间下标
}
if (right >= mid + 1) { // 如果右区间仍有元素可用,创建右子节点
curNode->right = new TreeNode(0);
nodeQue.push(curNode->right); // 将右子节点加入待处理队列
leftQue.push(mid + 1); // 更新左区间下标
rightQue.push(right); // 更新右区间下标
}
}
return root;
}
};