Convert Sorted Array to Binary Search Tree -- LeetCode

本文介绍如何将有序数组转换为平衡二分查找树,采用递归方式选取中间元素作为根节点,再分别递归构造左右子树。此方法时间复杂度为O(n),空间复杂度为O(logn)。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原题链接:  http://oj.leetcode.com/problems/convert-sorted-array-to-binary-search-tree/  
这道题是二分查找树的题目,要把一个有序数组转换成一颗二分查找树。其实从本质来看,如果把一个数组看成一棵树(也就是以中点为根,左右为左右子树,依次下去)数组就等价于一个二分查找树。所以如果要构造这棵树,那就是把中间元素转化为根,然后递归构造左右子树。所以我们还是用二叉树递归的方法来实现,以根作为返回值,每层递归函数取中间元素,作为当前根和赋上结点值,然后左右结点接上左右区间的递归函数返回值。时间复杂度还是一次树遍历O(n),总的空间复杂度是栈空间O(logn)加上结果的空间O(n),额外空间是O(logn),总体是O(n)。代码如下: 
public TreeNode sortedArrayToBST(int[] num) {
    if(num==null || num.length==0)
        return null;
    return helper(num,0,num.length-1);
}
private TreeNode helper(int[] num, int l, int r)
{
    if(l>r)
        return null;
    int m = (l+r)/2;
    TreeNode root = new TreeNode(num[m]);
    root.left = helper(num,l,m-1);
    root.right = helper(num,m+1,r);
    return root;
}
这是一道不错的题目,模型简单,但是考察了遍历和二分查找树的数据结构,比较适合在电面中出现,类似的题目有 Convert Sorted List to Binary Search Tree ,是这道题非常好的后续问题,不同数据结构,实现上也会有所不同,有兴趣的朋友可以看看哈。
<think>嗯,用户让我分析这段将有序数组转换为平衡二叉搜索的代码。我需要先仔细理解代码的结构和工作原理。首先,代码是用C语言写的,结构体TreeNode应该包含val、left和right指针。函数sortedArrayToBST接收一个数组和大小,然后调用dfs函数递归构建。 接下来,我得理解dfs函数的作用。参数是nums数组、left和right边界。递归终止条件是left等于right,返回NULL。这说明区间是左闭右开的,比如当left为0,right为0时没有元素。然后计算中间位置m,这里用的是left + (right - left)/2,这样可以防止整数溢出。中间节点作为当前子的根节点,然后递归处理左半部分和右半部分。 用户提到要说明中间节点是左还是右,这里需要看中间值的计算方式。例如,当数组长度为偶数时,比如4个元素,中间位置应该是第二个元素(索引从0开始)。因为(right - left)/2在left为0,right为4时是2,所以m=2。这时候左边是0到2,右边是3到4。所以中间节点选的是靠左的位置吗?或者要看具体例子。 然后需要举例运行,分奇数和偶数情况。比如奇数情况用数组[1,2,3],偶数用[1,2,3,4]。对于奇数数组,中间是2,左右子各一个节点。对于偶数数组,中间是第二个元素,即3的位置?或者当数组是0到3索引时,中间是1还是2?比如数组是[1,2,3,4],left=0,right=4,计算m=0 + (4-0)/2=2,所以中间是索引2的元素3?那左子是0到2(元素1和2),右是3到4(元素4)。这时候根是3,左子的根是1还是2?需要再仔细分析递归过程。 另外,用户的问题中提到中间节点是选择靠左还是靠右。根据代码中m的计算方式,当区间长度是偶数时,比如长度为4,中间位置是2(比如0-4区间,m=2),这时取的是中间偏右的位置吗?比如数组索引0-3的元素,中间位置是1.5,但整数除法会取1吗?或者原题中的数组可能为0到numsSize-1,而right是numsSize,所以当数组长度为偶数时,比如numsSize=4,right是4,中间是0 + (4-0)/2=2,所以索引2是第三个元素,在四个元素中是中间偏右的位置吗? 例如,数组是[1,2,3,4],索引0到3。调用dfs(0,4),m=2,元素3作为根。然后左子处理0到2(索引0和1),即元素1和2,此时m=0+ (2-0)/2=1,所以根是2。左子是0到1(元素1),右是1+1=2到2,返回NULL。这样形成的结构是根3,左子根2,左子的左是1,右为空;右子根4。这样整个的高度平衡吗? 另一个例子,数组长度3,索引0-2。初始调用dfs(0,3),m=1,元素2作为根。左子处理0到1,即元素1,右处理2到3(元素3)。这样形成的是平衡的。 所以作者使用的是每次选取中间偏右的节点作为根?比如当数组长度为偶数时,中间位置取的是右边的那个。比如四个元素时,选第三个元素作为根。这可能是因为计算方式中的整数除法是向下取整。例如,当区间长度是4时,中间位置是2,而索引0-3中的中间位置是1.5,所以整数除法得到的是1吗?或者原代码中的区间是左闭右开,所以当left=0,right=4时,元素是0到3,四个元素,中间位置是0+4/2=2,索引2的元素是第三个元素。这时候确实中间偏右。 那在注释中需要说明,当数组长度为偶数时,中间节点选择的是中间两个中的后一个,即靠右的节点。例如,数组长度为4时,中间是第二个索引(0-based),比如数组元素1、2、3、4,中间是3? 或者需要再仔细计算一下。例如,当数组长度为4时,初始left=0,right=4。m=0 + (4-0)/2=2。那么nums[m]是第三个元素。所以当数组是升序排列时,选中间靠右的节点作为根,这样左子的元素比根小,右子的元素比根大,这样满足二叉搜索的条件。 然后用户要求举例运行,奇数和偶数各一次。比如奇数例子数组是[1,2,3],中间选2,左右各一个元素。偶数例子数组是[1,2,3,4],中间选3作为根,左子处理0到2的元素1和2,中间选2作为根,再左子是1,右子为空。右子处理3到4的元素4。最终形成的结构可能需要图示。 另外,代码中的递归函数每次都将中间元素作为根,然后递归构建左右子,这样生成的是高度平衡的,因为左右子的节点数最多相差一个。 总结下来,代码的工作原理是通过递归选取中间元素作为当前子的根,确保左右子的高度差不超过1,从而实现平衡。当数组长度为偶数时,中间元素选取的是中间两个中的靠右的那个,即中间位置的计算方式是使用整数除法,例如长度为4时,中间索引是2。</think>### 代码分析 这段代码将一个**升序数组**转换为**高度平衡的二叉搜索**(AVL),核心思想是**递归选取中间元素作为根节点**,保证左右子高度差不超过1。 --- ### 代码结构与注释 ```c // 递归构建平衡二叉搜索 // nums: 输入数组,left: 当前区间左边界(包含),right: 当前区间右边界(不包含) struct TreeNode* dfs(int* nums, int left, int right) { if (left == right) { // 区间为空,递归终止 return NULL; } int m = left + (right - left) / 2; // 计算中间位置(向下取整) struct TreeNode* node = malloc(sizeof(struct TreeNode)); node->val = nums[m]; // 中间元素作为根节点 node->left = dfs(nums, left, m); // 递归构建左子 node->right = dfs(nums, m + 1, right); // 递归构建右子 return node; } // 主函数入口 struct TreeNode* sortedArrayToBST(int* nums, int numsSize) { return dfs(nums, 0, numsSize); // 初始区间为[0, numsSize) } ``` --- ### 核心逻辑 1. **中间节点选择**: 每次选取区间中点 `nums[m]` 作为根节点。 - **奇偶处理**:当区间长度为偶数时,`m` 的计算公式 `left + (right - left)/2` **向下取整**,导致中间节点偏向区间左侧。 - 例如,区间 `[0,4)`(元素为`[1,2,3,4]`)的中间索引是 `2`(值为3),而非中间两个元素的右侧。 2. **递归构建子**: - 左子区间为 `[left, m)`,右子区间为 `[m+1, right)`。 - 通过左闭右开区间避免重复处理元素。 --- ### 运行示例 #### 示例1(奇数长度数组:`[1, 2, 3]`) ```text 初始调用: dfs(0, 3) 区间[0,3)的中间m=1,根节点值为2 ├── 左子: dfs(0,1) → m=0,根节点值为1 │ ├── 左子: dfs(0,0) → NULL │ └── 右子: dfs(1,1) → NULL └── 右子: dfs(2,3) → m=2,根节点值为3 ├── 左子: dfs(2,2) → NULL └── 右子: dfs(3,3) → NULL 最终结构: 2 / \ 1 3 ``` #### 示例2(偶数长度数组:`[1, 2, 3, 4]`) ```text 初始调用: dfs(0,4) 区间[0,4)的中间m=2,根节点值为3 ├── 左子: dfs(0,2) │ └── 中间m=1,根节点值为2 │ ├── 左子: dfs(0,1) → m=0,根节点值为1 │ │ ├── 左子: NULL │ │ └── 右子: NULL │ └── 右子: dfs(2,2) → NULL └── 右子: dfs(3,4) → m=3,根节点值为4 ├── 左子: dfs(3,3) → NULL └── 右子: dfs(4,4) → NULL 最终结构: 3 / \ 2 4 / 1 ``` --- ### 关键总结 - **中间节点偏向左侧**:通过 `m = left + (right - left)/2` 计算,**向下取整**导致中间节点在偶数长度区间中靠左(例如 `[1,2,3,4]` 的中间是2)。 - **平衡性保证**:每次递归将区间均分,左右子高度差不超过1。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值