【每日力扣31】将有序数组转换为二叉搜索树

一、题目[LeetCode-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,3] 和 [3,1] 都是高度平衡二叉搜索树。

 

提示:

  • 1 <= nums.length <= 10^4
  • -10^4 <= nums[i] <= 10^4
  • nums 按 严格递增 顺序排列

 

二、思路

递归:中序遍历转为层次遍历

1)首先处理数组nums,将其严格递增的排列顺序更改为最终所求平衡BST树的层次遍历顺序(即根值为nums[0],根的左儿子的值为nums[1],根的右儿子的值为nums[2],根的左儿子的左儿子的值为nums[3])。

这里使用递归的方法。先找出数组nums的中间数nums[nums.size()/2](该数将作为生成的数的根节点),将其赋值给新数组的第一位newNums[0],然后递归处理子区间nums[0, middle - 1]和nums[middle + 1, nums.size()-1],找出的中位数分别作为newNums[1], newNums[2]。

具体流程:首先创建一个新数组newNum,大小拓展为刚好超过num.size()的2^i-1(这样是最近的完全二叉树的个数),多出来的位置便是二叉树的nullptr,这样有利于后续递归子函数调用的统一化。然后编写递归子函数,其中有一个参数index,意为插入到newNum中的位置。它的递归方式为:对于当前节点在newNum中的位置为index,则左儿子的位置为2index+1,右儿子位置为2index+2。

2)将nums转化为二叉搜索树。这里再次使用递归的方法。对于任意节点nums[i],其左右子节点值分别为nums[2i+1],nums[2i+2](在不越界的情况下)。利用这个性质我们便可进行递归构造二叉搜索树。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    const int INTOFNULL = 10001;
    TreeNode* sortedArrayToBST(vector<int>& nums) {
        vector<int> newNums = moveArray(nums);
        return creatTreeNodes(newNums, 0);
    }
    vector<int> moveArray(vector<int>& nums){//创建新数组newNum,里面是将nums的元素由递增排序变为层次遍历的排序。(大小拓展到恰好大于nums.size()的完全二叉树大小,多出来的位置即是nullptr)并在这里调用重载递归子函数。
        int n = nums.size();
        int i = 1;
        while(pow(2,i)-1 < n)
            i++;
        vector<int> newNums(pow(2,i)-1);//找出离n最近的恰好大于n的完全二叉树的节点个数,作为新数组的size
        moveArray(nums, newNums, 0, nums.size()-1, 0);//重载递归子函数
        return newNums;
    }
    void moveArray(vector<int>& nums, vector<int>& newNums, int left, int right, int index){
        if(index >= newNums.size())//递归基————如果index越界了直接返回
            return;
        if(left > right)//当左边界大于右边界时,调用它的上一个函数是middle==left或middle==right,到这里是空节点了
        {
            newNums[index] = INTOFNULL;//表明newNums的该位置在二叉树中是nullptr
            return;
        }
        int middle = (left + right)/2;
        newNums[index] = nums[middle];//将中间值插入新数组的对应位置index
        moveArray(nums, newNums, left, middle-1, index*2+1);//递归调用。这里左儿子的index便是2index+1
        moveArray(nums, newNums, middle+1, right, index*2+2);//递归调用。这里右儿子的index便是2index+2
    }
    TreeNode* creatTreeNodes(vector<int>& nums, int index){
        if(index >= nums.size() || nums[index] == INTOFNULL)
            return nullptr;
        return new TreeNode(nums[index], creatTreeNodes(nums, index*2+1), creatTreeNodes(nums, index*2+2));
    }
};

三、官方题解(来源:力扣(LeetCode)) 

方法一:中序遍历,总是选择中间位置左边的数字作为根节点

选择中间位置左边的数字作为根节点,则根节点的下标为 mid=(left+right)/2,此处的除法为整数除法。

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;
    }
};

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/convert-sorted-array-to-binary-search-tree/solution/jiang-you-xu-shu-zu-zhuan-huan-wei-er-cha-sou-s-33/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

复杂度分析

  • 时间复杂度:O(n),其中 n 是数组的长度。每个数字只访问一次。
  • 空间复杂度:O(logn),其中 n 是数组的长度。空间复杂度不考虑返回值,因此空间复杂度主要取决于递归栈的深度,递归栈的深度是 O(logn)。

方法二:中序遍历,总是选择中间位置右边的数字作为根节点

选择中间位置右边的数字作为根节点,则根节点的下标为 mid=(left+right+1)/2,此处的除法为整数除法。

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 + 1) / 2;

        TreeNode* root = new TreeNode(nums[mid]);
        root->left = helper(nums, left, mid - 1);
        root->right = helper(nums, mid + 1, right);
        return root;
    }
};

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/convert-sorted-array-to-binary-search-tree/solution/jiang-you-xu-shu-zu-zhuan-huan-wei-er-cha-sou-s-33/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

复杂度分析

  • 时间复杂度:O(n),其中 n 是数组的长度。每个数字只访问一次。
  • 空间复杂度:O(logn),其中 n 是数组的长度。空间复杂度不考虑返回值,因此空间复杂度主要取决于递归栈的深度,递归栈的深度是 O(logn)。

方法三:中序遍历,选择任意一个中间位置数字作为根节点

选择任意一个中间位置数字作为根节点,则根节点的下标为 mid=(left+right)/2 和 mid=(left+right+1)/2 两者中随机选择一个,此处的除法为整数除法。

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 + rand() % 2) / 2;

        TreeNode* root = new TreeNode(nums[mid]);
        root->left = helper(nums, left, mid - 1);
        root->right = helper(nums, mid + 1, right);
        return root;
    }
};

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/convert-sorted-array-to-binary-search-tree/solution/jiang-you-xu-shu-zu-zhuan-huan-wei-er-cha-sou-s-33/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

复杂度分析

  • 时间复杂度:O(n),其中 n 是数组的长度。每个数字只访问一次。
  • 空间复杂度:O(logn),其中 n 是数组的长度。空间复杂度不考虑返回值,因此空间复杂度主要取决于递归栈的深度,递归栈的深度是 O(logn)。

四、学习心得

①二叉搜索树BST的中序遍历结果即为一个严格递增的数组

因此本题其实给的即是所求二叉搜索树的中序遍历结果。对于一个二叉树的中序遍历结果:

  • 中序遍历结果不能唯一地确定二叉搜索树
  • 中序遍历结果不能唯一地确定平衡二叉搜索树,但是对于每一棵子树的创建时,根从中序数组区间中的选取位置各有最多两种选择(对于子树的节点个数(中序遍历数组对应的区间长度)为奇数时,根只有一个选择——数组区间中间值;为偶数时,根有两种选择——中间位置的左边值和中间位置的右边值)

因此,官方解法中,一种思路给出了三种可供选择的根选取的方法。

②数组越界问题

在递归地选定数组区间(涉及到参数left和right)时,递归基为left恰好比right大于1,这时应当在递归子函数的前面先用if语句写出对应的特殊操作——返回null指针,否则将会越界。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值