目录
- 题目描述
- 思路及实现
- 方式一:递归中值法
- 思路
- 代码实现
- Java版本
- C语言版本
- Python3版本
- Golang版本
- 复杂度分析
- 方式二:迭代法
- 思路
- 代码实现
- Java实现
- Python实现
- C++实现
- Go版本
- 复杂度分析
- 总结
- 总结
- 相似题目
- 标签(题目类型):树,二叉搜索树,数组
题目描述
给定一个有序整数数组,将其转换为一棵高度平衡的二叉搜索树。
原题:LeetCode 108. 将有序数组转换为二叉搜索树
思路及实现
方式一:递归中值法
思路
我们可以使用递归的方法来构建二叉搜索树。每次递归,我们选择数组的中间元素作为根节点,然后将数组分为左右两部分,分别构建左子树和右子树。
代码实现
Java版本
class TreeNode {int val;TreeNode left;TreeNode right;TreeNode(int x) {val = x;}
}public class Solution {public TreeNode sortedArrayToBST(int[] nums) {if (nums == null || nums.length == 0) {return null;}return buildBST(nums, 0, nums.length - 1);}private TreeNode buildBST(int[] nums, int start, int end) {if (start > end) {return null;}int mid = (start + end) / 2;TreeNode root = new TreeNode(nums[mid]);root.left = buildBST(nums, start, mid - 1);root.right = buildBST(nums, mid + 1, end);return root;}
}
说明:
TreeNode
是树节点的定义。sortedArrayToBST
方法是入口点,首先检查输入数组的有效性,然后调用buildBST
方法进行递归构建。buildBST
方法是递归构建二叉搜索树的核心。它首先找到中间索引mid
,然后创建根节点,并递归地为左半部分和右半部分构建子树。
C语言版本
#include <stdlib.h>struct TreeNode {int val;struct TreeNode *left;struct TreeNode *right;
};struct TreeNode* buildBST(int* nums, int start, int end) {if (start > end) {return NULL;}int mid = (start + end) / 2;struct TreeNode* root = (struct TreeNode*)malloc(sizeof(struct TreeNode));root->val = nums[mid];root->left = buildBST(nums, start, mid - 1);root->right = buildBST(nums, mid + 1, end);return root;
}struct TreeNode* sortedArrayToBST(int* nums, int numsSize){if (nums == NULL || numsSize == 0) {return NULL;}return buildBST(nums, 0, numsSize - 1);
}
说明:
- 这里使用 C 语言标准库中的
malloc
来动态分配内存。- 其余逻辑与 Java 版本类似。
Python3版本
class TreeNode:def __init__(self, val=0, left=None, right=None):self.val = valself.left = leftself.right = rightclass Solution:def sortedArrayToBST(self, nums: List[int]) -> TreeNode:if not nums:return Nonedef buildBST(start, end):if start > end:return Nonemid = (start + end) // 2root = TreeNode(nums[mid])root.left = buildBST(start, mid - 1)root.right = buildBST(mid + 1, end)return rootreturn buildBST(0, len(nums) - 1)
说明:
- 使用 Python 类来定义树节点。
sortedArrayToBST
方法是入口点,它定义了一个内部递归函数buildBST
来构建二叉搜索树。
Golang版本
package maintype TreeNode struct {Val intLeft *TreeNodeRight*TreeNode
}func sortedArrayToBST(nums []int) *TreeNode {if len(nums) == 0 {return nil}return buildBST(nums, 0, len(nums)-1)
}func buildBST(nums []int, start, end int) *TreeNode {if start > end {return nil}mid := (start + end) / 2root := &TreeNode{Val: nums[mid]}root.Left = buildBST(nums, start, mid-1)root.Right = buildBST(nums, mid+1, end)return root
}func main() {// 示例用法nums := []int{-10, -3, 0, 5, 9}root := sortedArrayToBST(nums)// 这里可以添加遍历树的代码来验证结果
}
说明:
- 使用 Go 语言的结构体来定义树节点。
sortedArrayToBST
方法是入口点,调用buildBST
方法来构建二叉搜索树。buildBST
方法是递归函数,用于构建二叉搜索树。
复杂度分析
- 时间复杂度:O(n),其中 n 是数组的长度。每个元素只被访问和处理一次。
- 空间复杂度:O(log n),平均情况下。递归调用栈的深度取决于树的高度,而平衡二叉搜索树的高度是 log n(以 2 为底)。在最坏的情况下(当输入数组已经排序并且完全不平衡时,例如数组中没有重复元素且递增或递减),空间复杂度会退化到 O(n)。但由于题目要求转换为高度平衡的二叉搜索树,因此平均情况下是 O(log n)。
方式二:迭代法
思路
- 初始化一个空栈,并确定数组的中点作为根节点。
- 将根节点压入栈中,并标记为已处理(如果需要的话)。
- 创建一个循环,直到栈为空:
- 弹出栈顶节点,并处理其左子树(如果存在):
- 找到左子树的起始索引和结束索引。
- 计算左子树的中点,并创建新的左子节点。
- 将左子节点设置为当前弹出节点的左孩子。
- 如果左子树非空,将左子节点压入栈中。
- 处理当前节点的右子树(如果存在):
- 找到右子树的起始索引和结束索引。
- 计算右子树的中点,并创建新的右子节点。
- 将右子节点设置为当前弹出节点的右孩子。
- 如果右子树非空,将右子节点压入栈中。
- 弹出栈顶节点,并处理其左子树(如果存在):
- 当栈为空时,表示所有的节点都已经处理完毕,BST构建完成。
代码实现
迭代法构建二叉搜索树(BST)通常不如递归方法直观,但可以通过维护一个栈来模拟递归过程。以下是迭代法构建BST的思路以及使用Python、Java、C++和JavaScript四种语言的实现,包括注释和代码说明。
Java实现
class TreeNode {int val;TreeNode left;TreeNode right;TreeNode(int val) {this.val = val;}
}public class Solution {public TreeNode sortedArrayToBST(int[] nums) {if (nums == null || nums.length == 0) return null;Deque<Object[]> stack = new ArrayDeque<>();int start = 0, end = nums.length - 1;TreeNode root = new TreeNode(nums[(start + end) / 2]);stack.push(new Object[]{root, start, end});while (!stack.isEmpty()) {Object[] curr = stack.poll();TreeNode node = (TreeNode) curr[0];int s = (int) curr[1], e = (int) curr[2];int mid = s + (e - s) / 2;if (s < mid) {TreeNode leftChild = new TreeNode(nums[mid - 1]);node.left = leftChild;stack.push(new Object[]{leftChild, s, mid - 1});}if (mid + 1 < e) {TreeNode rightChild = new TreeNode(nums[mid + 1]);node.right = rightChild;stack.push(new Object[]{rightChild, mid + 1, e});}}return root;}
}
Python实现
class TreeNode:def __init__(self, val=0, left=None, right=None):self.val = valself.left = leftself.right = rightdef sortedArrayToBST(nums):if not nums:return Nonestack, start, end = [], 0, len(nums) - 1root = TreeNode(nums[start + (end - start) // 2])stack.append((root, start, end))while stack:node, start, end = stack.pop()mid = (start + end) // 2if start < mid:left_child = TreeNode(nums[mid - 1]) # 左子树的根(实际上是mid-1,因为mid是根)node.left = left_childstack.append((left_child, start, mid - 1))if mid + 1 < end:right_child = TreeNode(nums[mid + 1]) # 右子树的根(实际上是mid+1)node.right = right_childstack.append((right_child, mid + 1, end))return root# 示例
nums = [-10, -3, 0, 5, 9]
root = sortedArrayToBST(nums)
# 遍历BST的代码略
C++实现
#include <stack>
#include <vector>struct TreeNode {int val;TreeNode *left;TreeNode *right;TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};TreeNode* sortedArrayToBST(std::vector<int>& nums) {if (nums.empty()) {return nullptr;}std::stack<std::pair<TreeNode*, std::pair<int, int>>> stk;int n = nums.size();TreeNode* root = new TreeNode(nums[n / 2]);stk.push({root, {0, n - 1}});while (!stk.empty()) {auto curr = stk.top();stk.pop();TreeNode* node = curr.first;int start = curr.second.first;int end = curr.second.second;int mid = start + (end - start) / 2;if (start < mid) {node->left = new TreeNode(nums[mid - 1]); // 左子树的根节点stk.push({node->left, {start, mid - 1}});}if (mid + 1 < end) {node->right = new TreeNode(nums[mid + 1]); // 右子树的根节点stk.push({node->right, {mid + 1, end}});}}return root;
}// 示例用法
int main() {std::vector<int> nums = {-10, -3, 0, 5, 9};TreeNode* root = sortedArrayToBST(nums);// 遍历BST的代码略return 0;
}
Go版本
当然,以下是Go语言版本的实现,其中包含了代码注释和说明:
package mainimport ("fmt"
)// TreeNode represents a node in a binary tree
type TreeNode struct {Val intLeft *TreeNodeRight *TreeNode
}// sortedArrayToBST converts a sorted array to a balanced binary search tree
func sortedArrayToBST(nums []int) *TreeNode {if len(nums) == 0 {return nil}// 定义辅助函数来构建BSTvar helper func(start, end int) *TreeNodehelper = func(start, end int) *TreeNode {if start > end {return nil}// 找到中间元素作为当前子树的根mid := (start + end) / 2root := &TreeNode{Val: nums[mid]}// 递归地构建左子树和右子树root.Left = helper(start, mid-1)root.Right = helper(mid+1, end)return root}// 调用辅助函数从整个数组构建BSTreturn helper(0, len(nums)-1)
}// inorderTraversal performs in-order traversal of a binary tree
func inorderTraversal(root *TreeNode) {if root == nil {return}inorderTraversal(root.Left)fmt.Println(root.Val)inorderTraversal(root.Right)
}func main() {nums := []int{-10, -3, 0, 5, 9}root := sortedArrayToBST(nums)// 验证BST是否构建正确,进行中序遍历inorderTraversal(root)
}
代码说明:
TreeNode
结构体定义了一个二叉树的节点,包含一个整数值Val
和两个指向左右子节点的指针Left
和Right
。
sortedArrayToBST
函数接受一个排序数组nums
,并返回一个指向平衡二叉搜索树根的指针。该函数定义了一个内部辅助函数helper
,它递归地构建BST。在
helper
函数中,我们首先检查start
是否大于end
,如果是,则返回nil
表示没有节点可构建。然后,我们找到中间元素mid
作为当前子树的根,并创建对应的TreeNode
。接下来,我们递归地调用helper
来构建左子树和右子树,并将返回的根节点分别赋值给当前节点的Left
和Right
。
inorderTraversal
函数用于验证BST是否构建正确。它使用中序遍历的方式遍历BST,并打印出每个节点的值。由于BST的性质,中序遍历将得到一个升序的序列。在
main
函数中,我们创建了一个排序数组nums
,并调用sortedArrayToBST
来构建BST。然后,我们调用inorderTraversal
来遍历BST并打印其节点值。
复杂度分析
对于上述四种语言的实现,它们的复杂度分析是相同的:
- 时间复杂度:O(n),其中n是数组的长度。每个元素只被访问和处理一次。
- 空间复杂度:O(log n),平均情况下。这是因为栈的深度取决于BST的高度,对于平衡BST来说,其高度是O(log n)。然而,在最坏的情况下(当输入数组已经排序并且完全不平衡时),空间复杂度会退化到O(n)。但在这个问题中,我们总是从数组的中点开始构建BST,因此通常能得到一个相对平衡的BST,使得空间复杂度保持在O(log n)的平均水平。
总结
总结
以下是对于两种构建平衡二叉搜索树(BST)的方式的总结,包括它们的优点、缺点、时间复杂度和空间复杂度,以及其他特点:
方式 | 优点 | 缺点 | 时间复杂度 | 空间复杂度 | 其他 |
---|---|---|---|---|---|
递归中值法 | 1. 代码简洁,逻辑清晰。 | 1. 在最坏情况下,递归调用栈可能会占用额外的 O(n) 空间。 | O(n) | 平均 O(log n),最坏 O(n) | 1. 易于理解和实现。 |
迭代栈方法 | 1. 避免了递归调用栈可能导致的空间问题。 | 1. 相比递归方法,代码可能略显复杂。 | O(n) | 平均 O(log n),最坏 O(n) | 1. 更节省空间,适合处理大数据集。 |
说明:
- 递归中值法:该方法直接通过递归找到数组的中值作为根节点,然后递归地构建左子树和右子树。这种方法逻辑清晰,易于理解,但在最坏情况下(如数组已排序),递归调用栈可能会占用大量的空间。
- 迭代栈方法:该方法使用栈来模拟递归过程,避免了递归调用栈的空间占用问题。但相比递归方法,代码可能略显复杂。在处理大数据集时,迭代栈方法通常更加节省空间。
- 时间复杂度:对于两种方法,时间复杂度都是 O(n),因为每个元素都需要被处理一次。
- 空间复杂度:对于递归中值法,空间复杂度取决于递归调用栈的深度,平均情况下为 O(log n),但在最坏情况下(如数组已排序)为 O(n)。对于迭代栈方法,空间复杂度同样是平均 O(log n),最坏 O(n),因为栈可能需要存储所有节点。但在实际应用中,迭代栈方法通常更加节省空间。
- 其他:两种方法各有特点,递归中值法更易于理解和实现,而迭代栈方法更节省空间。在选择使用哪种方法时,需要根据具体的应用场景和需求来权衡。
相似题目
相似题目 | 难度 | 链接 |
---|---|---|
leetcode 107. 二叉树的层次遍历 II | 中等 | 力扣-107 |
leetcode 109. 有序链表转换二叉搜索树 | 中等 | 力扣-109 |
leetcode 110. 平衡二叉树 | 简单 | 力扣-110 |