0.前言
有了之前的对于二叉树的简单准备,我终于有信心开启这个的讲解。我不再贴出自己的解法,我们直接讲解官方题解。
1.官方题解
class Solution {
public:
TreeNode* sortedArrayToBST(vector<int>& nums) {
return helper(nums, 0, nums.size() - 1);
}
TreeNode* helper(vector<int>& nums, int left, int right) {
if (left > right) {
return nullptr;
}
// 总是选择中间位置左边的数字作为根节点
int mid = (left + right) / 2;
TreeNode* root = new TreeNode(nums[mid]);
root->left = helper(nums, left, mid - 1);
root->right = helper(nums, mid + 1, right);
return root;
}
};
官方题解的思路很简单,就是要每次递归生成一个子树部分。
1.1.仅切割中点
但其实,第一次做这个题目,我们是想不到这么流畅,我们只能先想出错误的解法,就像下面这样:
int mid = (left+right)/2;
treeNode->left = helper(nums,left,mid);
treeNode->right = helper(nums,mid,right);
return treeNode;
在这段代码里,我们看到了什么呢?有切割中点的思路,知道要左右子树去分别递归,但是这么写。总觉得少了点什么,接下来,我们以 [0,1,2,3]
为例,一步一步接近正确的解题思路。
因为数组的下标是从0
开始的,所以这里我们为了保持一致,一直使用数组下标进行表示。
我们发现,当我们持续向下分割,分割到[0,1]
[1,2]
[2,3]
时,程序无法再分割下去了。
因为我们只进行了分割,没有去创建这棵树。
1.2.创建根节点
那么,我们该怎么办呢?我们要把每个根节点给它创建出来啊。
int mid = (left+right)/2;
TreeNode* root = new TreeNode(nums[mid]);
treeNode->left = helper(nums,left,mid);
treeNode->right = helper(nums,mid,right);
return treeNode;
1.3.走出重复创建的怪圈
我们发现还是不对,根节点在上一次已经创建出来了,下一次递归时,它又创建了。
我们发现如果我们持续传入mid
对应的数据,它就会一直存在着。然后不停地在计算mid
的过程中被计算出来。
这样就一直创建相同的值的根节点了,这可不行,我们下一次传入的时候不能再要这个位置。
int mid = (left + right) / 2;
TreeNode* root = new TreeNode(nums[mid]);
root->left = helper(nums, left, mid - 1);
root->right = helper(nums, mid + 1, right);
return root;
1.4.补足临界条件
现在我们的关键的节点已经能连接上了,但是有个问题,最后的叶子结点,左右子树应该为NULL
,如何判断我们递归到了叶子结点呢?
我们发现啊,有两种情况,我们会创建一个结点:
- 当 left<rightleft \lt rightleft<right 时,我们取 mid=⌊left+right2⌋mid = \left \lfloor {\frac{left + right}{2}} \right \rfloormid=⌊2left+right⌋ ,此时创建 midmidmid 。
- 当 left=rightleft = rightleft=right 时,我们取上式时,其实就是直接取 leftleftleft 或 rightrightright 。
除此之外的情况,我们应该都是为NULL
的情况,故得:
class Solution {
public:
TreeNode* sortedArrayToBST(vector<int>& nums) {
return helper(nums, 0, nums.size() - 1);
}
TreeNode* helper(vector<int>& nums, int left, int right) {
if (left > right) {
return nullptr;
}
// 总是选择中间位置左边的数字作为根节点
int mid = (left + right) / 2;
TreeNode* root = new TreeNode(nums[mid]);
root->left = helper(nums, left, mid - 1);
root->right = helper(nums, mid + 1, right);
return root;
}
};
2.总结
2.1.粗调-精调
我们在处理本题时,使用了一种解题思路:就是 粗调-精调 的解题思路。
我们掌握了大致上的解题思路——每次生成根节点,然后递归生成左右子树。然后便开始编码,在编码的过程中,我们会不断发现自己思路中错误的地方,然后调整每个逻辑的子单元。
也许你会说,这就是简单的分治法。当然也不能说你错,只是在探讨解题思路时,会涉及先掌握整体思路,然后再进行局部修改。
2.2.每次递归的产出是什么?
本次解题还在问我们一个问题:每次递归的产出应该是什么?
我们做一步递归,要完成哪些内容?
在本题中,我们每次递归,要产生一个节点或者是返回NULL
,还要根据递归关系,关联左右子树。
假如我们把这种问题想清楚,解题也会快不少。
3.结语
你还想到了什么?欢迎你在评论区和其他读者讨论。