【LeetCode】108. 将有序数组转换为二叉搜索树
文章目录
- 108. 将有序数组转换为二叉搜索树
- 题目描述
- 示例 1:
- 示例 2:
- 提示:
- 解题思路
- 问题分析
- 算法思想
- 方法一:递归(中间偏左)
- 方法二:递归(中间偏右)
- 方法三:迭代法(栈模拟)
- 方法四:中序遍历模拟
- 复杂度对比
- 关键点总结
- 扩展问题
- 相关题目
- 完整题解代码
108. 将有序数组转换为二叉搜索树
题目描述
给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 平衡 二叉搜索树。
示例 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,null,3] 和 [3,1] 都是高度平衡二叉搜索树。
提示:
- 1 <= nums.length <= 10^4
- -10^4 <= nums[i] <= 10^4
- nums 按 严格递增 顺序排列
解题思路
问题分析
这是一道经典的二叉搜索树(BST)构建问题。关键点:
- 输入特点:数组已经按升序排列
- 输出要求:构建平衡二叉搜索树
- 核心洞察:有序数组的中序遍历恰好是BST的中序遍历结果
算法思想
由于数组已排序,我们可以利用分治思想:
- 选择中间元素作为根节点(保证平衡)
- 左半部分递归构建左子树
- 右半部分递归构建右子树
这样构建的树自然满足:
- BST性质:左子树 < 根 < 右子树
- 平衡性:左右子树高度差不超过1
方法一:递归(中间偏左)
核心思想:每次选择中间位置(偏左)作为根节点
graph TDA[开始: nums=[-10,-3,0,5,9]] --> B[选择中间索引 mid=2, 值=0]B --> C[0作为根节点]C --> D[左子数组: -10,-3]C --> E[右子数组: 5,9]D --> F[选择mid=0, 值=-3]F --> G[-3作为左子树根]G --> H[左: -10]G --> I[右: null]E --> J[选择mid=0, 值=5]J --> K[5作为右子树根]K --> L[左: null]K --> M[右: 9]style A fill:#e1f5ffstyle C fill:#bbdefbstyle G fill:#90caf9style K fill:#90caf9
算法步骤:
flowchart TDStart([输入: nums, left, right]) --> Check{left > right?}Check -->|是| ReturnNil[返回 nil]Check -->|否| CalcMid[计算中间索引<br/>mid = left + right / 2]CalcMid --> CreateNode[创建根节点<br/>root = &TreeNode{Val: nums[mid]}]CreateNode --> BuildLeft[递归构建左子树<br/>root.Left = sortedArrayToBST nums, left, mid-1]BuildLeft --> BuildRight[递归构建右子树<br/>root.Right = sortedArrayToBST nums, mid+1, right]BuildRight --> Return([返回 root])ReturnNil --> End([结束])Return --> Endstyle Start fill:#e8f5e9style Check fill:#fff3e0style CreateNode fill:#bbdefbstyle BuildLeft fill:#c8e6c9style BuildRight fill:#c8e6c9style Return fill:#c5cae9
时间复杂度:O(n) - 每个节点访问一次
空间复杂度:O(log n) - 递归栈深度
方法二:递归(中间偏右)
核心思想:选择中间偏右的位置作为根节点
mid := left + (right - left + 1) / 2
这样会生成另一种平衡的BST结构。
方法三:迭代法(栈模拟)
核心思想:使用栈模拟递归过程
flowchart TDStart([输入: nums]) --> Init[初始化栈<br/>压入 0, len-1, nil, true]Init --> Loop{栈非空?}Loop -->|否| End([返回根节点])Loop -->|是| Pop[弹出栈顶<br/>left, right, parent, isLeft]Pop --> Check{left > right?}Check -->|是| LoopCheck -->|否| CalcMid[mid = left + right / 2]CalcMid --> CreateNode[创建节点<br/>node = &TreeNode{Val: nums[mid]}]CreateNode --> CheckParent{parent == nil?}CheckParent -->|是| SetRoot[设置为根节点]CheckParent -->|否| CheckLeft{isLeft?}CheckLeft -->|是| SetLeft[parent.Left = node]CheckLeft -->|否| SetRight[parent.Right = node]SetRoot --> PushRightSetLeft --> PushRightSetRight --> PushRight[压入右子树参数<br/>mid+1, right, node, false]PushRight --> PushLeft[压入左子树参数<br/>left, mid-1, node, true]PushLeft --> Loopstyle Start fill:#e8f5e9style CreateNode fill:#bbdefbstyle Loop fill:#fff3e0style End fill:#c5cae9
时间复杂度:O(n)
空间复杂度:O(log n)
方法四:中序遍历模拟
核心思想:按中序遍历的顺序构建BST
这种方法需要提前知道树的大小,然后在中序遍历位置填充节点值。
复杂度对比
| 方法 | 时间复杂度 | 空间复杂度 | 特点 |
|---|---|---|---|
| 递归(中间偏左) | O(n) | O(log n) | 代码简洁,易理解 |
| 递归(中间偏右) | O(n) | O(log n) | 生成不同的平衡树 |
| 迭代法 | O(n) | O(log n) | 避免递归栈溢出 |
| 中序遍历 | O(n) | O(log n) | 模拟BST构建过程 |
关键点总结
- 选择中点:保证平衡性的关键
- 递归边界:left > right 时返回 nil
- 分治思想:问题规模减半
- BST性质:利用数组有序性
扩展问题
- 如何验证构建的树是否平衡?
- 如何构建最小高度的BST?
- 如果数组有重复元素怎么处理?
- 如何构建完全二叉搜索树?
相关题目
- LeetCode 109:有序链表转换二叉搜索树
- LeetCode 110:平衡二叉树
- LeetCode 1382:将二叉搜索树变平衡
完整题解代码
package mainimport ("fmt""math""strings"
)// TreeNode 二叉树节点定义
type TreeNode struct {Val intLeft *TreeNodeRight *TreeNode
}// ==================== 方法一:递归(中间偏左)====================
// 时间复杂度:O(n),每个元素访问一次
// 空间复杂度:O(log n),递归栈深度
func sortedArrayToBST(nums []int) *TreeNode {return buildBST(nums, 0, len(nums)-1)
}func buildBST(nums []int, left, right int) *TreeNode {// 递归终止条件if left > right {return nil}// 选择中间位置(偏左)作为根节点mid := left + (right-left)/2root := &TreeNode{Val: nums[mid]}// 递归构建左右子树root.Left = buildBST(nums, left, mid-1)root.Right = buildBST(nums, mid+1, right)return root
}// ==================== 方法二:递归(中间偏右)====================
// 选择中间偏右的元素作为根节点,会生成不同的平衡BST
func sortedArrayToBST2(nums []int) *TreeNode {return buildBST2(nums, 0, len(nums)-1)
}func buildBST2(nums []int, left, right int) *TreeNode {if left > right {return nil}// 选择中间位置(偏右)作为根节点mid := left + (right-left+1)/2root := &TreeNode{Val: nums[mid]}root.Left = buildBST2(nums, left, mid-1)root.Right = buildBST2(nums, mid+1, right)return root
}// ==================== 方法三:迭代法(栈模拟)====================
// 使用栈模拟递归过程
type StackNode struct {left intright intparent *TreeNodeisLeft bool
}func sortedArrayToBST3(nums []int) *TreeNode {if len(nums) == 0 {return nil}var root *TreeNodestack := []StackNode{{0, len(nums) - 1, nil, true}}for len(stack) > 0 {// 弹出栈顶元素curr := stack[len(stack)-1]stack = stack[:len(stack)-1]if curr.left > curr.right {continue}// 创建当前节点mid := curr.left + (curr.right-curr.left)/2node := &TreeNode{Val: nums[mid]}// 处理父节点连接if curr.parent == nil {root = node} else if curr.isLeft {curr.parent.Left = node} else {curr.parent.Right = node}// 先压入右子树,再压入左子树(栈是后进先出)stack = append(stack, StackNode{mid + 1, curr.right, node, false})stack = append(stack, StackNode{curr.left, mid - 1, node, true})}return root
}// ==================== 方法四:中序遍历模拟 ====================
// 按中序遍历的顺序构建BST
func sortedArrayToBST4(nums []int) *TreeNode {if len(nums) == 0 {return nil}idx := 0return inorderBuild(nums, &idx, 0, len(nums)-1)
}func inorderBuild(nums []int, idx *int, left, right int) *TreeNode {if left > right {return nil}mid := left + (right-left)/2// 先构建左子树leftNode := inorderBuild(nums, idx, left, mid-1)// 创建根节点root := &TreeNode{Val: nums[*idx]}*idx++root.Left = leftNode// 再构建右子树root.Right = inorderBuild(nums, idx, mid+1, right)return root
}// ==================== 辅助函数 ====================// 中序遍历验证BST
func inorderTraversal(root *TreeNode) []int {if root == nil {return []int{}}result := []int{}result = append(result, inorderTraversal(root.Left)...)result = append(result, root.Val)result = append(result, inorderTraversal(root.Right)...)return result
}// 层序遍历(用于显示树结构)
func levelOrder(root *TreeNode) []interface{} {if root == nil {return []interface{}{}}result := []interface{}{}queue := []*TreeNode{root}for len(queue) > 0 {node := queue[0]queue = queue[1:]if node == nil {result = append(result, nil)} else {result = append(result, node.Val)queue = append(queue, node.Left)queue = append(queue, node.Right)}}// 移除末尾的 nilfor len(result) > 0 && result[len(result)-1] == nil {result = result[:len(result)-1]}return result
}// 检查是否为平衡二叉树
func isBalanced(root *TreeNode) bool {return checkHeight(root) != -1
}func checkHeight(root *TreeNode) int {if root == nil {return 0}leftHeight := checkHeight(root.Left)if leftHeight == -1 {return -1}rightHeight := checkHeight(root.Right)if rightHeight == -1 {return -1}if abs(leftHeight-rightHeight) > 1 {return -1}return max(leftHeight, rightHeight) + 1
}// 检查是否为BST
func isValidBST(root *TreeNode) bool {return validateBST(root, math.MinInt64, math.MaxInt64)
}func validateBST(root *TreeNode, min, max int) bool {if root == nil {return true}if root.Val <= min || root.Val >= max {return false}return validateBST(root.Left, min, root.Val) && validateBST(root.Right, root.Val, max)
}// 获取树的高度
func getHeight(root *TreeNode) int {if root == nil {return 0}return max(getHeight(root.Left), getHeight(root.Right)) + 1
}// 树形打印
func printTree(root *TreeNode, prefix string, isLeft bool) {if root == nil {return}fmt.Print(prefix)if isLeft {fmt.Print("├── ")} else {fmt.Print("└── ")}fmt.Println(root.Val)if root.Left != nil || root.Right != nil {if root.Left != nil {printTree(root.Left, prefix+getTreePrefix(isLeft, true), true)} else {fmt.Println(prefix + getTreePrefix(isLeft, true) + "├── nil")}if root.Right != nil {printTree(root.Right, prefix+getTreePrefix(isLeft, false), false)} else {fmt.Println(prefix + getTreePrefix(isLeft, false) + "└── nil")}}
}func getTreePrefix(isLeft, hasNext bool) string {if isLeft {return "│ "}return " "
}func abs(x int) int {if x < 0 {return -x}return x
}func max(a, b int) int {if a > b {return a}return b
}// ==================== 测试函数 ====================func testCase(name string, nums []int) {fmt.Printf("\n========== %s ==========\n", name)fmt.Printf("输入: %v\n", nums)// 方法一:中间偏左root1 := sortedArrayToBST(nums)fmt.Println("\n方法一(中间偏左):")fmt.Printf("层序遍历: %v\n", levelOrder(root1))fmt.Printf("中序遍历: %v\n", inorderTraversal(root1))fmt.Printf("是否平衡: %v\n", isBalanced(root1))fmt.Printf("是否BST: %v\n", isValidBST(root1))fmt.Printf("树高度: %d\n", getHeight(root1))fmt.Println("树结构:")printTree(root1, "", false)// 方法二:中间偏右root2 := sortedArrayToBST2(nums)fmt.Println("\n方法二(中间偏右):")fmt.Printf("层序遍历: %v\n", levelOrder(root2))fmt.Printf("中序遍历: %v\n", inorderTraversal(root2))fmt.Printf("是否平衡: %v\n", isBalanced(root2))// 方法三:迭代法root3 := sortedArrayToBST3(nums)fmt.Println("\n方法三(迭代法):")fmt.Printf("层序遍历: %v\n", levelOrder(root3))fmt.Printf("中序遍历: %v\n", inorderTraversal(root3))fmt.Printf("是否平衡: %v\n", isBalanced(root3))// 方法四:中序遍历root4 := sortedArrayToBST4(nums)fmt.Println("\n方法四(中序遍历):")fmt.Printf("层序遍历: %v\n", levelOrder(root4))fmt.Printf("中序遍历: %v\n", inorderTraversal(root4))fmt.Printf("是否平衡: %v\n", isBalanced(root4))
}// ==================== 扩展功能 ====================// 从BST转回有序数组
func bstToSortedArray(root *TreeNode) []int {return inorderTraversal(root)
}// 获取BST中第k小的元素
func kthSmallest(root *TreeNode, k int) int {arr := inorderTraversal(root)if k > 0 && k <= len(arr) {return arr[k-1]}return -1
}// 将BST转换为更平衡的BST(重新构建)
func balanceBST(root *TreeNode) *TreeNode {arr := inorderTraversal(root)return sortedArrayToBST(arr)
}// 比较两种方法生成的树结构差异
func compareTreeStructures(nums []int) {fmt.Printf("\n========== 比较不同方法生成的树结构 ==========\n")fmt.Printf("输入数组: %v\n", nums)root1 := sortedArrayToBST(nums)root2 := sortedArrayToBST2(nums)fmt.Println("\n中间偏左策略:")printTree(root1, "", false)fmt.Println("\n中间偏右策略:")printTree(root2, "", false)fmt.Printf("\n两棵树的中序遍历相同: %v\n",strings.Trim(strings.Join(strings.Fields(fmt.Sprint(inorderTraversal(root1))), ","), "[]") ==strings.Trim(strings.Join(strings.Fields(fmt.Sprint(inorderTraversal(root2))), ","), "[]"))
}// 性能测试
func performanceTest() {fmt.Println("\n========== 性能测试 ==========")sizes := []int{100, 1000, 10000}for _, size := range sizes {nums := make([]int, size)for i := 0; i < size; i++ {nums[i] = i}fmt.Printf("\n数组大小: %d\n", size)// 测试方法一root1 := sortedArrayToBST(nums)fmt.Printf("方法一 - 树高度: %d, 期望高度: %d\n",getHeight(root1), int(math.Ceil(math.Log2(float64(size+1)))))// 测试方法二root2 := sortedArrayToBST2(nums)fmt.Printf("方法二 - 树高度: %d, 期望高度: %d\n",getHeight(root2), int(math.Ceil(math.Log2(float64(size+1)))))}
}func main() {// 测试用例1:示例1testCase("测试用例1:基本情况", []int{-10, -3, 0, 5, 9})// 测试用例2:示例2testCase("测试用例2:两个元素", []int{1, 3})// 测试用例3:单个元素testCase("测试用例3:单个元素", []int{1})// 测试用例4:奇数个元素testCase("测试用例4:奇数个元素", []int{1, 2, 3, 4, 5, 6, 7})// 测试用例5:偶数个元素testCase("测试用例5:偶数个元素", []int{1, 2, 3, 4, 5, 6})// 测试用例6:连续数字testCase("测试用例6:连续数字", []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9})// 测试用例7:负数testCase("测试用例7:负数", []int{-10, -5, -3, 0, 5, 10})// 扩展功能测试fmt.Println("\n========== 扩展功能测试 ==========")nums := []int{1, 2, 3, 4, 5, 6, 7}root := sortedArrayToBST(nums)fmt.Printf("原数组: %v\n", nums)fmt.Printf("BST转回数组: %v\n", bstToSortedArray(root))fmt.Printf("第3小的元素: %d\n", kthSmallest(root, 3))// 比较不同构建策略compareTreeStructures([]int{1, 2, 3, 4, 5, 6, 7})// 性能测试performanceTest()
}
