【LeetCode】Convert Sorted Array to Binary Search Tree(将有序数组转换为二叉搜索树)

博客围绕LeetCode第108题展开,题目是将升序有序数组转换为高度平衡二叉搜索树。介绍了用二分法、分治法解题,强调计算中值时避免加法溢出的细节。博主尝试迭代法但因进出栈顺序问题未完成,还提及堆栈迭代做法。

这道题是LeetCode里的第108道题。

题目描述:

将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。

本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。

示例:

给定有序数组: [-10,-3,0,5,9],

一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树:

      0
     / \
   -3   9
   /   /
 -10  5

二分法,分治法。我们可以考虑把一个大的问题分解成为几个小的问题。

首先二分法肯定是确认的,因为要保证树结构是有序的,这个我亲自验证过了。

另外还需注意求中值 m 的时候,最好不要 (l + r) / 2 这样计算,可能会加法溢出,最安全的方法是 m = l + (r - l) / 2,就如同计算:x * x > y 不如 x > y / x 好。这些细节都有可能是面试的加分项,更厉害点可以把 m = l + (r - l) / 2 改成 m = l + ((r - l) >> 1)。

解题代码:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode sortedArrayToBST(int[] nums) {
        return nums == null ? null : buildTree(nums, 0, nums.length - 1);
    }

    private TreeNode buildTree(int[] nums, int l, int r) {
        if (l > r) {
            return null;
        }
        // int m = l + ((r - l) >> 1);
        int m = l + (r - l) / 2;
        TreeNode root = new TreeNode(nums[m]);
        root.left = buildTree(nums, l, m - 1);
        root.right = buildTree(nums, m + 1, r);
        return root;
    }
}

提交结果:

个人总结:

这道题我不想使用递归做,使用迭代却因为无法处理进出栈的顺序而无法完成任务。

堆栈迭代做法:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    private class Node{
        int low, up;
        TreeNode t;
        Node(int l, int p, TreeNode node){
            low = l;
            up = p;
            t = node;
        }
    }
    public TreeNode sortedArrayToBST(int[] nums) {
        if(nums == null || nums.length == 0)return null;

        // initialize
        Stack<Node> stack = new Stack<Node>();
        TreeNode root = new TreeNode(nums[(nums.length - 1)/2]);
        Node rootNode = new Node(0, nums.length - 1, root);
        stack.push(rootNode);

        // iteration
        while(!stack.isEmpty()) {
            Node node = stack.pop();
            int middle = (node.low + node.up) / 2;// cut half for [low, up]

            // [low, middle - 1]
            if(middle - 1 >= node.low) {
                TreeNode leftnode = new TreeNode(nums[(middle-1+node.low)/2]);
                node.t.left = leftnode;
                Node left = new Node(node.low, middle - 1, leftnode);
                stack.push(left);
            }

            // [middle + 1, up]
            if(middle + 1 <= node.up) {
                TreeNode rightnode = new TreeNode(nums[(middle+1+node.up)/2]);
                node.t.right = rightnode;
                Node right = new Node(middle+1, node.up, rightnode);
                stack.push(right);
            }
        }
		
        return root;
    }
}

太强了!

<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。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值