uniapp 二叉搜索树 aop

#41

今日计划

1.写uniapp 示例代码 2*h 10-12.

uniapp

vue3

在线练习

再看会视频 1h 19.

:3h

2.写点Java 算法

二叉搜素树习题

avl树

中午先看视频课 1h

14.30 - 17.30

3h

:4h

3.Java 技术栈

4h

spring aop 看完 ssm框架下那个

maven 高级视频 20h的那个

:4h

4.小兔鲜儿day1

::10h

规划 aop 小兔项目 maven高级

今日源码

uniapp
demo2
以下是针对 **0 基础** 学习者的详细代码说明,包含 **Vue 基础语法** 讲解 + 逐行注释,用最通俗的语言拆解每个知识点:

---

### 完整代码(带 0 基础友好注释)
```html
<!-- 
  1. <template>:Vue 模板区域,写 HTML 结构的地方
     - 所有要渲染到页面的内容都写在这里
     - 可以理解为“Vue 里的 HTML 部分”
-->
<template>
  <!-- 最外层容器,相当于 HTML 里的 <div>,用来包裹页面内容 -->
  <view class="container">

    <!-- 
      2. 文本渲染:{{ 变量 }} 是 Vue 的“插值语法”
         - 把 JavaScript 里的变量显示到页面上
         - 类似“把变量的值打印到这里”
    -->
    <text class="title">{{ title }}</text>

    <!-- 
      3. 条件渲染:v-if 
         - 类似“如果 showMessage 是 true,就显示这个 <view>”
         - 反之则隐藏(Vue 会直接从 DOM 里删掉)
    -->
    <view v-if="showMessage" class="message">
      {{ message }}
    </view>

    <!-- 
      4. 事件绑定:@click 
         - 给按钮绑定点击事件,点击时执行 changeTitle 函数
         - 类似 HTML 里的 onclick,但 Vue 用 @ 符号更简洁
    -->
    <button @click="changeTitle" type="primary">
      修改标题
    </button>

    <!-- 商品列表区域 -->
    <view class="list">
      <!-- 加载状态提示:数据没加载完时显示 -->
      <view v-if="isLoading" class="loading">
        正在加载更多数据...
      </view>

      <!-- 
        5. 列表渲染:v-for 
           - 遍历 displayedItems 数组,把每个元素渲染成 <view>
           - :key="index" 是为了让 Vue 高效更新列表(必须写)
           - 可以理解为“循环创建多个相同结构的 DOM”
      -->
      <view 
        v-for="(item, index) in displayedItems" 
        :key="index" 
        class="list-item"
      >
        {{ index + 1 }}. {{ item.name }} - {{ item.price }}元
        
        <!-- 操作按钮:点击时执行 operateItem 函数,把当前 item 传过去 -->
        <button class="operate-btn" @click="operateItem(item)">操作</button>

      </view>

      <!-- 加载更多按钮:如果还有数据没加载,就显示 -->
      <button 
        v-if="hasMoreData" 
        @click="loadMore" 
        type="default" 
        class="load-more-btn"
      >
        加载更多
      </button>

    </view>

    <!-- 计算属性展示:调用 calculateTotalPrice 函数显示总价格 -->
    <view class="total-price">
      商品总价格:{{ calculateTotalPrice() }}元
    </view>

    <!-- 表单区域 -->
    <view class="form">
      <!-- 
        6. 双向绑定:v-model 
           - 输入框内容和 inputValue 变量“实时同步”
           - 输入框变,变量变;变量变,输入框也变
      -->
      <u-input v-model="inputValue" placeholder="请输入内容" />
      <button @click="addItem" type="default">添加</button>

    </view>

  </view>

</template>

<!-- 
  7. <script setup>:Vue 的逻辑区域,写 JavaScript 代码的地方
     - 所有变量、函数都写在这里
     - 可以理解为“Vue 里的 JavaScript 部分”
-->
<script setup>
// 8. 导入 Vue 提供的响应式工具
//    - ref:让变量变成“响应式”(数据变了,页面自动更新)
//    - onMounted:页面加载完成后执行的钩子(类似 DOM 加载完)
//    - computed:计算属性(高级用法,当前代码里用了函数替代,可忽略)
import { ref, onMounted } from 'vue';

// 9. 定义“响应式变量”
//    - 用 ref 包裹后,变量变化会触发页面更新
const title = ref('Hello uni-app'); // 页面标题
const message = ref('欢迎学习uniapp Vue3开发'); // 欢迎消息
const showMessage = ref(true); // 控制欢迎消息是否显示
const items = ref([ // 商品列表原始数据
  { id: 1, name: '苹果', price: 5 },
  { id: 2, name: '香蕉', price: 3 },
  { id: 3, name: '橙子', price: 4 }
]);
const inputValue = ref(''); // 表单输入框内容
const displayedItems = ref(items.value.slice(0, 3)); // 当前展示的商品(分页用)
const isLoading = ref(false); // 是否正在加载更多
const hasMoreData = ref(true); // 是否还有更多数据

// 10. 生命周期钩子:onMounted
//     - 页面渲染完成后自动执行
onMounted(() => {
  console.log('页面加载完成'); // 控制台打印日志
  // 模拟异步请求:2秒后给商品列表加一条数据
  setTimeout(() => {
    items.value.push({ id: 4, name: '葡萄', price: 8 });
  }, 2000);
});

// 11. 定义函数(方法)
//     - 点击按钮时会调用这些函数

// 修改标题和隐藏消息
const changeTitle = () => { 
  title.value = '狗叫'; // 直接修改响应式变量的值
  showMessage.value = false; // 隐藏欢迎消息
};

// 添加新商品
const addItem = () => { 
  // 去除输入框首尾空格,如果为空就不执行
  if (!inputValue.value.trim()) return; 

  // 创建新商品对象
  const newItem = {
    id: items.value.length + 1, // id 自增
    name: inputValue.value, // 用输入框的内容
    price: Math.floor(Math.random() * 20) // 随机价格
  };

  items.value.push(newItem); // 加到原始数据
  displayedItems.value.push(newItem); // 加到当前展示的数据
  inputValue.value = ''; // 清空输入框
};

// 加载更多商品(模拟分页)
const loadMore = () => { 
  isLoading.value = true; // 显示“加载中”状态

  // 模拟异步请求:1秒后执行
  setTimeout(() => { 
    const startIndex = displayedItems.value.length; // 当前最后一条的索引
    const endIndex = startIndex + 3; // 加载3条新数据
    const newItems = items.value.slice(startIndex, endIndex); // 从原始数据里截取

    displayedItems.value.push(...newItems); // 加到当前展示的数据
    isLoading.value = false; // 隐藏“加载中”状态

    // 判断是否还有更多数据
    if (endIndex >= items.value.length) { 
      hasMoreData.value = false; // 没有更多了
    }
  }, 1000);
};

// 操作商品(示例:删除)
const operateItem = (item) => { 
  console.log('操作商品:', item); // 控制台打印当前商品

  // 找到商品在原始数据里的索引
  const index = items.value.findIndex(i => i.id === item.id);
  if (index!== -1) { // 如果找到
    items.value.splice(index, 1); // 从原始数据删除
    // 从当前展示的数据删除
    displayedItems.value.splice(displayedItems.value.findIndex(i => i.id === item.id), 1); 
  }
};

// 计算商品总价格(遍历求和)
const calculateTotalPrice = () => { 
  // reduce 是数组方法,遍历 items 求和
  return items.value.reduce((sum, item) => sum + item.price, 0);
};
</script>

<!-- 
  12. <style scoped>:Vue 的样式区域,写 CSS 的地方
       - scoped:样式只作用于当前组件(不会影响其他组件)
       - 可以理解为“Vue 里的 CSS 部分”
-->
<style scoped>
/* 最外层容器样式 */
.container {
  padding: 30rpx; /* rpx 是 uni-app 适配多端的单位,类似 px */
  background-color: #f8f8f8;
}

/* 标题样式 */
.title {
  font-size: 40rpx;
  font-weight: bold;
  color: #333;
  text-align: center;
  margin-bottom: 30rpx;
}

/* 欢迎消息样式 */
.message {
  background-color: #e6f7ff;
  color: #1890ff;
  padding: 20rpx;
  border-radius: 10rpx;
  margin-bottom: 30rpx;
}

/* 列表容器样式 */
.list {
  margin-top: 30rpx;
}

/* 列表项样式 */
.list-item {
  padding: 20rpx;
  border-bottom: 1rpx solid #eee;
  font-size: 28rpx;
  display: flex; /* 弹性布局,让内容和按钮左右排列 */
  justify-content: space-between; /* 内容和按钮两端对齐 */
  align-items: center; /* 垂直居中 */
}

/* 操作按钮样式 */
.operate-btn {
  font-size: 24rpx;
  padding: 10rpx 20rpx;
}

/* 加载中提示样式 */
.loading {
  text-align: center;
  padding: 20rpx;
  color: #999;
}

/* 加载更多按钮样式 */
.load-more-btn {
  display: block; /* 让按钮独占一行 */
  margin: 20rpx auto; /* 水平居中 */
}

/* 总价格样式 */
.total-price {
  margin-top: 20rpx;
  font-size: 28rpx;
  color: #ff5722;
  font-weight: bold;
}

/* 表单样式 */
.form {
  display: flex; /* 让输入框和按钮并排 */
  margin-top: 30rpx;
}

/* 输入框样式 */
.form u-input {
  flex: 1; /* 输入框占满剩余空间 */
  margin-right: 20rpx;
}
</style>

```

---

### 0 基础核心语法拆解(Vue 基础 + uni-app 特色)

#### 1. **模板语法(`<template>` 里的内容)**
- **插值语法**:`{{ 变量 }}`  
  - 作用:把 JavaScript 里的变量显示到页面上。  
  - 示例:`{{ title }}` 会显示 `title` 变量的值(比如 `Hello uni-app`)。  

- **指令(以 `v-` 开头的特殊属性)**  
  - `v-if="条件"`:条件为 `true` 时显示该元素,否则隐藏。  
    - 示例:`v-if="showMessage"` 控制欢迎消息是否显示。  
  - `v-for="(item, index) in 数组"`:循环渲染数组里的每个元素。  
    - 示例:`v-for="(item, index) in displayedItems"` 把 `displayedItems` 里的每个商品渲染成 `<view>`。  
  - `v-model="变量"`:双向绑定,输入框内容和变量实时同步。  
    - 示例:`v-model="inputValue"` 输入框输入的内容会直接存到 `inputValue` 里。  


#### 2. **逻辑区域(`<script setup>` 里的内容)**
- **响应式变量**:用 `ref` 包裹的变量  
  - 作用:变量变化时,页面会自动更新。  
  - 示例:`const title = ref('Hello uni-app')`,修改 `title.value` 时,页面上的 `{{ title }}` 会同步变化。  

- **生命周期钩子**:`onMounted`  
  - 作用:页面加载完成后自动执行的代码。  
  - 示例:`onMounted(() => { ... })` 里的代码会在页面渲染完成后执行(比如模拟异步请求)。  

- **函数定义**:直接写 `const 函数名 = () => { ... }`  
  - 作用:处理用户交互(比如点击按钮)。  
  - 示例:`changeTitle` 函数在按钮点击时执行,修改 `title` 和 `showMessage`。  


#### 3. **样式区域(`<style scoped>` 里的内容)**
- **`scoped` 属性**:样式只作用于当前组件,不会影响其他组件。  
- **`rpx` 单位**:uni-app 专用单位,自动适配不同设备屏幕(类似 `px`,但更智能)。  
- **Flex 布局**:用 `display: flex` 实现弹性布局,方便排列元素(比如让按钮和文字并排)。  


#### 4. **uni-app 特色**
- **`view`、`text`、`button` 等组件**:uni-app 封装的跨平台组件,类似 HTML 的 `div`、`span`、`button`,但能更好地适配小程序、App 等。  
- **`u-input` 组件**:`uView` 组件库的输入框(需要项目引入 `uView` 库才能用,普通 `input` 可用 `<input>` 或 uni-app 的 `<uni-input>`)。  


---

### 学习路径建议(0 基础友好)
1. **先理解核心流程**:  
   - 页面结构写在 `<template>`(Vue 的 HTML)。  
   - 变量、函数写在 `<script setup>`(Vue 的 JavaScript)。  
   - 样式写在 `<style scoped>`(Vue 的 CSS)。  

2. **逐个突破语法**:  
   - 先掌握 `{{ 变量 }}`、`v-if`、`v-for`、`v-model`(最常用)。  
   - 再学 `ref`、`onMounted`(理解响应式和生命周期)。  

3. **动手改代码**:  
   - 试着修改 `title` 的初始值、`changeTitle` 的逻辑,看页面变化。  
   - 删改商品列表,观察 `v-for` 的渲染效果。  

4. **结合实际需求扩展**:  
   - 比如给“操作”按钮加个确认删除的提示(用 `uni.showModal`)。  
   - 把模拟的异步请求换成真实的接口调用(用 `uni.request`)。  


这样拆分后,0 基础也能一步步理解 **Vue 基础语法** 和 **uni-app 组件逻辑**,遇到不懂的可以再针对性提问~

算法
Leetcode450
以下是添加了详细注释的Java版本代码:

```java
class Solution {
    /**
     * 删除二叉搜索树中的指定节点,并保持BST性质
     * 
     * @param root 当前子树的根节点
     * @param key  要删除的节点值
     * @return 删除节点后的新子树根节点
     */
    public TreeNode deleteNode(TreeNode root, int key) {
        // 1. 基本情况:如果当前节点为空,直接返回null
        if (root == null) return null;
        
        // 2. 递归查找要删除的节点
        if (key < root.val) {
            // 目标节点在左子树中
            root.left = deleteNode(root.left, key);
        } else if (key > root.val) {
            // 目标节点在右子树中
            root.right = deleteNode(root.right, key);
        } else {
            // 3. 找到目标节点,处理删除逻辑
            // 情况1:无左子树,直接返回右子树
            if (root.left == null) {
                return root.right;
            }
            // 情况2:无右子树,直接返回左子树
            else if (root.right == null) {
                return root.left;
            }
            // 情况3:有两个子节点
            else {
                // 3.1 找到右子树中的最小节点(中序后继节点)
                TreeNode minNode = findMin(root.right);
                // 3.2 用后继节点的值替换当前节点的值
                root.val = minNode.val;
                // 3.3 递归删除右子树中的后继节点
                root.right = deleteNode(root.right, minNode.val);
            }
        }
        
        // 返回更新后的当前子树根节点
        return root;
    }
    
    /**
     * 查找以node为根的子树中的最小节点
     * BST的最小节点是最左侧节点
     */
    private TreeNode findMin(TreeNode node) {
        while (node.left != null) {
            node = node.left;
        }
        return node;
    }
}

// 二叉树节点定义
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int x) { val = x; }
}
```

### 核心逻辑解释

1. **递归查找策略**:
   - BST特性:左子树节点值 < 根节点值 < 右子树节点值
   - 通过比较key与当前节点值的大小,决定往左/右子树递归

2. **删除节点的三种情况**:
   ```
   情况1:待删除节点无左子树
       root
        / \
      null  R
   → 直接返回右子树R

   情况2:待删除节点无右子树
       root
        / \
       L  null
   → 直接返回左子树L

   情况3:待删除节点有两个子树
       root
        / \
       L   R
   → 找到右子树R的最小节点S(中序后继)
   → 用S的值替换root的值
   → 递归删除R中的S节点
   ```

3. **中序后继节点的作用**:
   - 中序遍历BST得到升序序列,当前节点的后继节点是刚好大于它的最小节点
   - 后继节点位于右子树的最左侧,保证替换后仍满足BST性质

4. **递归的回溯过程**:
   - 递归删除子节点后,上层节点的left/right指针会自动更新
   - 无需显式处理父节点指针,递归返回值会自动完成链接

### 复杂度分析

- **时间复杂度**:O(h),h为树的高度
  - 最坏情况(退化为链表):O(n)
  - 平均情况(平衡树):O(log n)

- **空间复杂度**:O(h),递归栈深度
  - 尾递归优化后可降至O(1)(需使用迭代实现)

这个算法巧妙地利用了BST的性质,通过递归和节点替换保持了树的结构完整性,是处理BST删除操作的经典方法。

Leetcode 701
LeetCode 701题是**二叉搜索树中的插入操作**,题目链接为:https://leetcode.cn/problems/insert-into-a-binary-search-tree/ 。下面为你详细讲解这道题。

### 题目描述
给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 保证原始二叉搜索树中不存在新值。

注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回任意有效的结果 。

### 示例
```
输入:root = [4,2,7,1,3], val = 5
输出:[4,2,7,1,3,5]
解释:另一个满足题目要求可以通过的树是 [5,2,7,1,3,null,4] ,
     这里给出其中一种情况。
```

### 解题思路
- **递归法**:
    - 二叉搜索树的特性是左子树的所有节点值都小于根节点值,右子树的所有节点值都大于根节点值。利用这个特性,从根节点开始,将待插入的值 `val` 与当前节点的值进行比较。
    - 如果 `val` 小于当前节点的值,那么就递归地在当前节点的左子树中插入;如果左子树为空,直接将新节点作为左子节点插入。
    - 如果 `val` 大于当前节点的值,那么就递归地在当前节点的右子树中插入;如果右子树为空,直接将新节点作为右子节点插入。
    - 递归结束的条件是遇到空节点,此时创建一个新节点并返回。
 - **迭代法**:
    - 同样基于二叉搜索树的特性,从根节点开始,使用一个循环来找到插入位置。
    - 循环中,将待插入的值 `val` 与当前节点的值进行比较,决定向左或向右移动。
    - 当找到一个空的子节点位置时,创建新节点并插入。

### 代码实现

**递归法(Java)**:
```java
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int x) { val = x; }
}

class Solution {
    public TreeNode insertIntoBST(TreeNode root, int val) {
        if (root == null) {
            // 遇到空节点,创建新节点并返回
            return new TreeNode(val);
        }
        if (val < root.val) {
            // 插入值小于当前节点值,递归插入左子树
            root.left = insertIntoBST(root.left, val);
        } else {
            // 插入值大于当前节点值,递归插入右子树
            root.right = insertIntoBST(root.right, val);
        }
        return root;
    }
}
```

**迭代法(Java)**:
```java
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int x) { val = x; }
}

class Solution {
    public TreeNode insertIntoBST(TreeNode root, int val) {
        if (root == null) {
            return new TreeNode(val);
        }
        TreeNode current = root;
        while (true) {
            if (val < current.val) {
                if (current.left == null) {
                    // 左子树为空,插入新节点
                    current.left = new TreeNode(val);
                    break;
                }
                current = current.left;
            } else {
                if (current.right == null) {
                    // 右子树为空,插入新节点
                    current.right = new TreeNode(val);
                    break;
                }
                current = current.right;
            }
        }
        return root;
    }
}
```

### 复杂度分析
- **时间复杂度**:
    - 递归法和迭代法的时间复杂度都是 $O(h)$,其中 $h$ 是二叉搜索树的高度。在平均情况下,二叉搜索树是平衡的,高度为 $O(\log n)$,$n$ 是树中节点的数量;在最坏情况下,树退化为链表,高度为 $O(n)$。
 - **空间复杂度**:
    - 递归法的空间复杂度取决于递归调用栈的深度,也就是树的高度,为 $O(h)$。在平均情况下是 $O(\log n)$,最坏情况下是 $O(n)$。
    - 迭代法的空间复杂度为 $O(1)$,因为只使用了常数级别的额外空间。 
E03. 查询节点-Leetcode 700
### LeetCode 700:二叉搜索树中的搜索

#### 题目描述
给定二叉搜索树(BST)的根节点和一个值 `val`,在树中查找是否存在值为 `val` 的节点,并返回该节点的子树;如果不存在,则返回 `null`。

#### 示例
```
输入:root = [4,2,7,1,3], val = 2
输出:[2,1,3]

输入:root = [4,2,7,1,3], val = 5
输出:null
```

#### 解题思路
由于二叉搜索树(BST)具有以下性质:
- 左子树所有节点值 < 根节点值
- 右子树所有节点值 > 根节点值
- 左右子树也都是BST

因此查找过程可以利用BST的有序性,通过以下方式实现:
1. **递归法**:从根节点开始,若当前节点值等于目标值则返回;若目标值更小则在左子树查找;若更大则在右子树查找
2. **迭代法**:使用循环代替递归,避免栈溢出问题,适合处理深树结构

#### 代码实现

**递归解法(Java)**
```java
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode() {}
    TreeNode(int val) { this.val = val; }
    TreeNode(int val, TreeNode left, TreeNode right) {
        this.val = val;
        this.left = left;
        this.right = right;
    }
}

class Solution {
    public TreeNode searchBST(TreeNode root, int val) {
        // 基本情况:当前节点为空或找到目标值
        if (root == null || root.val == val) {
            return root;
        }
        // 根据BST性质决定搜索方向
        return val < root.val ? searchBST(root.left, val) : searchBST(root.right, val);
    }
}
```

**迭代解法(Java)**
```java
class Solution {
    public TreeNode searchBST(TreeNode root, int val) {
        // 从根节点开始迭代查找
        while (root != null) {
            if (root.val == val) {
                return root; // 找到目标节点
            } else if (val < root.val) {
                root = root.left; // 目标值更小,搜索左子树
            } else {
                root = root.right; // 目标值更大,搜索右子树
            }
        }
        return null; // 未找到目标节点
    }
}
```

#### 复杂度分析
- **时间复杂度**:O(h),其中h为树的高度
  - 平均情况:BST平衡时,h = log(n),n为节点数
  - 最坏情况:树退化为链表时,h = n
- **空间复杂度**:
  - 递归解法:O(h),递归栈深度
  - 迭代解法:O(1),仅使用常数级额外空间

#### 算法优化点
1. **提前终止**:找到目标值后立即返回,无需继续搜索
2. **非递归实现**:对于深树结构,迭代法可避免栈溢出风险
3. **尾递归优化**:若语言支持(如Python的`functools.lru_cache`),可优化递归性能

#### 常见错误处理
1. **空指针检查**:在访问`root.left`或`root.right`前,需确保`root`不为空
2. **边界条件**:处理只有根节点、只有左/右子树的特殊情况
3. **值比较逻辑**:严格遵循BST的大小关系,避免方向判断错误

该问题是BST的基础操作,掌握搜索、插入、删除这三大操作是理解BST数据结构的核心。

验证二叉搜索树-Leetcode 98
### LeetCode 98: 验证二叉搜索树

#### 题目描述
给定一个二叉树,判断其是否是一个有效的二叉搜索树(BST)。

有效二叉搜索树定义如下:
- 左子树上所有节点的值均小于根节点的值
- 右子树上所有节点的值均大于根节点的值
- 左右子树也必须是二叉搜索树

#### 示例
```
输入: [2,1,3]
输出: true

输入: [5,1,4,null,null,3,6]
输出: false
解释: 根节点值为5,右子节点值为4,但4小于5,因此不是有效BST
```

#### 解题思路

##### 方法一:中序遍历法
二叉搜索树的重要特性是:**中序遍历结果为严格递增序列**。因此可以通过以下步骤验证:
1. 对二叉树进行中序遍历
2. 检查遍历结果是否严格递增

##### 方法二:递归边界法
为每个节点维护允许的数值范围:
1. 根节点的初始范围是(-∞, +∞)
2. 左子节点的范围是(父节点最小值, 父节点值)
3. 右子节点的范围是(父节点值, 父节点最大值)
4. 如果当前节点值不在允许范围内,则不是有效BST

#### 代码实现

##### 中序遍历法(Java)
```java
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode() {}
    TreeNode(int val) { this.val = val; }
    TreeNode(int val, TreeNode left, TreeNode right) {
        this.val = val;
        this.left = left;
        this.right = right;
    }
}

class Solution {
    // 存储中序遍历结果
    private List<Integer> inorderList = new ArrayList<>();
    
    public boolean isValidBST(TreeNode root) {
        if (root == null) return true;
        
        // 1. 中序遍历
        inorderTraversal(root);
        
        // 2. 检查是否严格递增
        for (int i = 1; i < inorderList.size(); i++) {
            if (inorderList.get(i) <= inorderList.get(i - 1)) {
                return false;
            }
        }
        return true;
    }
    
    // 中序遍历辅助方法
    private void inorderTraversal(TreeNode root) {
        if (root == null) return;
        inorderTraversal(root.left);
        inorderList.add(root.val);
        inorderTraversal(root.right);
    }
}
```

##### 递归边界法(Java)
```java
class Solution {
    public boolean isValidBST(TreeNode root) {
        // 初始范围:最小值为Integer.MIN_VALUE,最大值为Integer.MAX_VALUE
        return isValidBST(root, Long.MIN_VALUE, Long.MAX_VALUE);
    }
    
    private boolean isValidBST(TreeNode root, long min, long max) {
        // 空节点视为有效BST
        if (root == null) return true;
        
        // 检查当前节点值是否在允许范围内
        if (root.val <= min || root.val >= max) {
            return false;
        }
        
        // 左子树范围:[min, root.val)
        // 右子树范围:(root.val, max)
        return isValidBST(root.left, min, root.val) && 
               isValidBST(root.right, root.val, max);
    }
}
```

#### 复杂度分析

| 方法 | 时间复杂度 | 空间复杂度 |
|------|------------|------------|
| 中序遍历法 | O(n) - 遍历所有节点 | O(n) - 存储中序遍历结果 |
| 递归边界法 | O(n) - 遍历所有节点 | O(h) - 递归栈深度,h为树高 |

#### 关键技巧

1. **避免整数溢出**:
   - 在递归边界法中使用`long`类型存储边界值,避免`Integer.MIN_VALUE`和`Integer.MAX_VALUE`导致的溢出问题

2. **严格递增判断**:
   - 必须确保每个节点值严格大于左子树所有值,严格小于右子树所有值
   - 注意不能使用"小于等于"或"大于等于"

3. **边界传递逻辑**:
   - 左子树的最大值是父节点值
   - 右子树的最小值是父节点值
   - 这个边界会随着递归深度逐层传递

#### 常见错误

1. **只比较直接子节点**:
   ```java
   // 错误示例:仅比较直接子节点,忽略了子树的整体约束
   return root.left.val < root.val && root.right.val > root.val;
   ```

2. **未正确处理边界值**:
   ```java
   // 错误示例:使用Integer范围可能导致溢出
   return isValidBST(root, Integer.MIN_VALUE, Integer.MAX_VALUE);
   ```

3. **混淆"小于"和"小于等于"**:
   ```java
   // 错误示例:允许节点值等于边界值
   if (root.val <= min || root.val >= max) {
   ```

这道题是二叉搜索树的基础验证问题,理解BST的性质和验证方法对解决更复杂的树问题非常重要。
求范围和-Leetcode 938
### LeetCode 938: 二叉搜索树的范围和

#### 题目描述
给定二叉搜索树的根节点 `root`,返回值位于范围 `[low, high]` 之间的所有节点的值的和。

#### 示例
```
输入: root = [10,5,15,3,7,null,18], low = 7, high = 15
输出: 32

输入: root = [10,5,15,3,7,13,18,1,null,6], low = 6, high = 10
输出: 23
```

#### 解题思路
由于二叉搜索树(BST)具有以下性质:
- 左子树所有节点值 < 根节点值
- 右子树所有节点值 > 根节点值
因此可以利用BST的有序性优化搜索过程:
1. **递归法**:根据当前节点值与范围的关系决定搜索方向
   - 若当前节点值 < low:只需要搜索右子树
   - 若当前节点值 > high:只需要搜索左子树
   - 若在范围内:累加当前节点值,并搜索左右子树
2. **迭代法**:使用栈模拟递归过程,避免栈溢出

#### 代码实现

**递归解法(Java)**
```java
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode() {}
    TreeNode(int val) { this.val = val; }
    TreeNode(int val, TreeNode left, TreeNode right) {
        this.val = val;
        this.left = left;
        this.right = right;
    }
}

class Solution {
    public int rangeSumBST(TreeNode root, int low, int high) {
        if (root == null) return 0;
        
        int sum = 0;
        // 当前节点值在范围内,累加并搜索左右子树
        if (root.val >= low && root.val <= high) {
            sum = root.val + rangeSumBST(root.left, low, high) + rangeSumBST(root.right, low, high);
        } 
        // 当前节点值小于low,只搜索右子树
        else if (root.val < low) {
            sum = rangeSumBST(root.right, low, high);
        } 
        // 当前节点值大于high,只搜索左子树
        else {
            sum = rangeSumBST(root.left, low, high);
        }
        
        return sum;
    }
}
```

**迭代解法(Java)**
```java
class Solution {
    public int rangeSumBST(TreeNode root, int low, int high) {
        if (root == null) return 0;
        
        int sum = 0;
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);
        
        while (!stack.isEmpty()) {
            TreeNode node = stack.pop();
            if (node != null) {
                // 当前节点值在范围内,累加到sum
                if (node.val >= low && node.val <= high) {
                    sum += node.val;
                }
                // 根据BST性质决定入栈顺序:先压右子节点,再压左子节点,保证栈顶是左子节点先处理
                if (node.val < high) {
                    stack.push(node.right);
                }
                if (node.val > low) {
                    stack.push(node.left);
                }
            }
        }
        
        return sum;
    }
}
```

#### 复杂度分析
- **时间复杂度**:O(n),其中n为树中节点数量。最坏情况下需要遍历所有节点。
- **空间复杂度**:
  - 递归解法:O(h),h为树的高度,递归栈的深度。
  - 迭代解法:O(h),栈的最大深度为树的高度。

#### 优化技巧
1. **剪枝策略**:
   - 当当前节点值 < low时,左子树所有节点值一定都 < low,无需搜索左子树
   - 当当前节点值 > high时,右子树所有节点值一定都 > high,无需搜索右子树

2. **遍历顺序**:
   - 迭代解法中采用"先右后左"的入栈顺序,确保左子树先被处理,符合中序遍历逻辑
   - 也可以采用中序遍历方式,只累加在范围内的节点值

#### 代码优化版本
```java
class Solution {
    // 优化后的递归解法,代码更简洁
    public int rangeSumBST(TreeNode root, int low, int high) {
        if (root == null) return 0;
        
        // 根据当前节点值与范围的关系决定返回值
        if (root.val < low) {
            return rangeSumBST(root.right, low, high);
        }
        if (root.val > high) {
            return rangeSumBST(root.left, low, high);
        }
        
        // 当前节点值在范围内,累加左右子树结果
        return root.val + rangeSumBST(root.left, low, high) + rangeSumBST(root.right, low, high);
    }
}
```

#### 常见错误
1. **边界条件处理**:
   - 忘记处理`root == null`的情况
   - 范围判断使用`<`和`>`而非`<=`和`>=`

2. **迭代栈操作顺序**:
   - 错误地先压左子节点再压右子节点,导致遍历顺序错误
   - 未根据节点值与范围的关系选择性入栈

3. **性能优化忽略**:
   - 未利用BST性质进行剪枝,导致遍历了不需要的节点

该问题充分体现了二叉搜索树的有序性优势,通过合理利用BST性质可以减少不必要的节点访问,提高算法效率。
根据前序遍历结果构造二叉搜索树-Leetcode 1008
### LeetCode 1008: 根据前序遍历构造二叉搜索树

#### 题目描述
给定一个整数数组 `preorder`,它是二叉搜索树(BST)的前序遍历结果,请构造并返回该二叉搜索树的根节点。

#### 示例
```
输入: preorder = [8,5,1,7,10,12]
输出: [8,5,10,1,7,null,12]

输入: preorder = [1,3]
输出: [1,null,3]
```

#### 解题思路
二叉搜索树的前序遍历结果具有以下特性:
- **根节点**:数组的第一个元素
- **左子树**:所有小于根节点的值组成的子数组
- **右子树**:所有大于根节点的值组成的子数组

因此,可以采用以下步骤递归构造BST:
1. **确定根节点**:前序遍历的第一个元素即为根节点
2. **划分左右子树**:找到第一个大于根节点的元素位置,将数组分为左右两部分
3. **递归构造**:分别对左右子数组递归构造左右子树

#### 代码实现

**递归解法(Java)**
```java
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode() {}
    TreeNode(int val) { this.val = val; }
    TreeNode(int val, TreeNode left, TreeNode right) {
        this.val = val;
        this.left = left;
        this.right = right;
    }
}

class Solution {
    public TreeNode bstFromPreorder(int[] preorder) {
        return buildTree(preorder, 0, preorder.length - 1);
    }
    
    private TreeNode buildTree(int[] preorder, int start, int end) {
        // 递归终止条件:起始索引大于结束索引
        if (start > end) {
            return null;
        }
        
        // 根节点为当前区间的第一个元素
        TreeNode root = new TreeNode(preorder[start]);
        
        // 找到第一个大于根节点值的位置,划分左右子树
        int index = start + 1;
        while (index <= end && preorder[index] < preorder[start]) {
            index++;
        }
        
        // 递归构造左右子树
        root.left = buildTree(preorder, start + 1, index - 1);
        root.right = buildTree(preorder, index, end);
        
        return root;
    }
}
```

**优化解法(递归+上界约束)**
```java
class Solution {
    private int index = 0;
    
    public TreeNode bstFromPreorder(int[] preorder) {
        return buildTree(preorder, Integer.MAX_VALUE);
    }
    
    private TreeNode buildTree(int[] preorder, int upperBound) {
        // 遍历完所有元素或当前元素超过上界时返回null
        if (index >= preorder.length || preorder[index] > upperBound) {
            return null;
        }
        
        // 创建当前节点
        TreeNode root = new TreeNode(preorder[index++]);
        
        // 递归构造左右子树:左子树的上界为当前节点值,右子树的上界为当前上界
        root.left = buildTree(preorder, root.val);
        root.right = buildTree(preorder, upperBound);
        
        return root;
    }
}
```

#### 复杂度分析

| 方法 | 时间复杂度 | 空间复杂度 |
|------|------------|------------|
| 递归划分法 | O(n²) | O(n) |
| 上界约束法 | O(n) | O(n) |

**优化点说明**:
- **递归划分法**:每次需要遍历数组寻找划分点,导致时间复杂度为O(n²)
- **上界约束法**:通过维护当前子树的上界,只需一次遍历即可完成构造,时间复杂度优化至O(n)

#### 关键技巧
1. **上界约束**:
   - 利用BST的特性,左子树所有节点值必须小于根节点值,右子树所有节点值必须小于父节点的上界
   - 通过递归传递上界参数,确保节点值符合BST约束

2. **全局索引**:
   - 使用全局变量`index`记录当前处理的元素位置,避免在递归过程中传递过多参数

3. **剪枝策略**:
   - 当元素值超过当前上界时,说明该元素不属于当前子树,直接返回null

#### 常见错误
1. **数组越界**:
   ```java
   // 错误示例:未检查index是否越界
   while (preorder[index] < preorder[start]) {
       index++;
   }
   ```

2. **左右子树划分错误**:
   ```java
   // 错误示例:错误的递归区间
   root.left = buildTree(preorder, start, index - 1); // 应为start+1
   root.right = buildTree(preorder, index, end + 1); // 应为end
   ```

3. **上界传递错误**:
   ```java
   // 错误示例:右子树的上界应为父节点的上界,而非父节点值
   root.right = buildTree(preorder, root.val); // 错误
   root.right = buildTree(preorder, upperBound); // 正确
   ```

该问题通过前序遍历序列构造BST,充分体现了BST的有序性特性。掌握递归构造和上界约束的技巧,对于解决其他树构造问题也非常有帮助。
二叉搜索树的最近公共祖先-Leetcode 235
### LeetCode 235: 二叉搜索树的最近公共祖先

#### 题目描述
给定一个二叉搜索树(BST), 找到该树中两个指定节点的最近公共祖先(LCA)。

最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

#### 示例
```
输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
输出: 6 
解释: 节点 2 和节点 8 的最近公共祖先是 6。

输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
输出: 2
解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。
```

#### 解题思路
由于二叉搜索树(BST)的特性:
- 左子树所有节点值 < 根节点值
- 右子树所有节点值 > 根节点值

因此,对于任意节点,其左右子树的节点值分布具有明确的有序性。可以利用这一特性快速找到最近公共祖先:
1. **递归法**:
   - 若当前节点值同时大于p和q的值,则LCA在左子树中
   - 若当前节点值同时小于p和q的值,则LCA在右子树中
   - 否则(p和q分别位于当前节点两侧或其中一个等于当前节点),当前节点即为LCA

2. **迭代法**:
   - 从根节点开始,根据当前节点值与p、q值的大小关系,决定向左或向右移动
   - 当找到第一个节点值位于p和q之间时,该节点即为LCA

#### 代码实现

**递归解法(Java)**
```java
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int x) { val = x; }
}

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        // 如果当前节点值大于p和q的值,LCA在左子树
        if (root.val > p.val && root.val > q.val) {
            return lowestCommonAncestor(root.left, p, q);
        }
        // 如果当前节点值小于p和q的值,LCA在右子树
        if (root.val < p.val && root.val < q.val) {
            return lowestCommonAncestor(root.right, p, q);
        }
        // 否则当前节点即为LCA
        return root;
    }
}
```

**迭代解法(Java)**
```java
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        TreeNode current = root;
        while (current != null) {
            // 如果当前节点值大于p和q的值,向左移动
            if (current.val > p.val && current.val > q.val) {
                current = current.left;
            }
            // 如果当前节点值小于p和q的值,向右移动
            else if (current.val < p.val && current.val < q.val) {
                current = current.right;
            }
            // 否则当前节点即为LCA
            else {
                return current;
            }
        }
        return null; // 理论上不会执行到这里,因为题目保证存在解
    }
}
```

#### 复杂度分析
- **时间复杂度**:O(h),其中h为树的高度。最坏情况下需要遍历树的高度。
- **空间复杂度**:
  - 递归解法:O(h),递归栈的深度为树的高度。
  - 迭代解法:O(1),只需要常数级的额外空间。

#### 关键技巧
1. **利用BST的有序性**:
   - 无需遍历整棵树,只需根据节点值的大小关系决定搜索方向
   - 当p和q分别位于当前节点两侧时,当前节点即为LCA

2. **迭代优化**:
   - 由于不需要回溯,迭代解法更高效,避免了递归栈的开销

3. **边界条件处理**:
   - 当p或q等于当前节点时,直接返回当前节点
   - 确保处理p和q的顺序不影响结果

#### 常见错误
1. **忽略BST特性**:
   ```java
   // 错误示例:未利用BST特性,遍历整棵树
   public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
       if (root == null || root == p || root == q) {
           return root;
       }
       TreeNode left = lowestCommonAncestor(root.left, p, q);
       TreeNode right = lowestCommonAncestor(root.right, p, q);
       // ...
   }
   ```

2. **方向判断错误**:
   ```java
   // 错误示例:混淆大于小于符号
   if (root.val < p.val && root.val < q.val) {
       return lowestCommonAncestor(root.left, p, q); // 错误:应向右移动
   }
   ```

3. **未处理p和q的顺序**:
   ```java
   // 错误示例:仅考虑p<q的情况
   if (root.val > p.val && root.val < q.val) { // 错误:未考虑p>q的情况
       return root;
   }
   ```

该问题通过利用BST的有序性,将LCA的查找时间复杂度优化到O(h),是BST特性的典型应用。掌握这种高效的查找方法对于解决其他树相关问题也非常有帮助。

题目汇总
package com.itheima.BST;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

public class BstLeetCode {

    //E01 450. 删除二叉搜索树中的节点
    public TreeNode deleteNode(TreeNode root, int key) {
        if (root == null) {
            return null;
        }
        if (key < root.val) {
            root.left = deleteNode(root.left, key);
        } else if (key > root.val) {
            root.right = deleteNode(root.right, key);
        } else {
            if (root.left == null) {
                return root.right;
            } else if (root.right == null) {
                return root.left;
            } else {
                TreeNode minNode = findMin(root.right);
                root.val = minNode.val;
                root.right = deleteNode(root.right, minNode.val);
            }
        }
        return root;
    }

    private TreeNode findMin(TreeNode node) {
        while (node.left != null) {
            node = node.left;
        }
        return node;
    }

    //Leetcode 701 二叉搜索树中的插入操作
    public TreeNode insertIntoBST(TreeNode root, int val) {
        if (root == null) {
            return new TreeNode(val);
        }
        if (val < root.val) {
            root.left = insertIntoBST(root.left, val);
        } else {
            root.right = insertIntoBST(root.right, val);
        }
        return root;
    }

    public TreeNode insertIntoBST2(TreeNode root, int val) {
        if (root == null) {
            return new TreeNode(val);
        }
        TreeNode current = root;
        while (true) {
            if (val < current.val) {
                if (current.left == null) {
                    current.left = new TreeNode(val);
                    break;
                } else {
                    current = current.left;
                }
            } else {
                if (current.right == null) {
                    current.right = new TreeNode(val);
                    break;
                } else {
                    current = current.right;
                }
            }
        }
        return root;
    }

    //Leetcode 700 二叉搜索树中的搜索
    public TreeNode searchBST(TreeNode root, int val) {
        if (root == null || root.val == val) {
            return root;
        }
        return val < root.val ? searchBST(root.left, val) : searchBST(root.right, val);
    }

    public TreeNode searchBST2(TreeNode root, int val) {
        while (root != null) {
            if (root.val == val) {
                return root;
            } else if (val < root.val) {
                root = root.left;
            } else {
                root = root.right;
            }
        }
        return null;
    }

    //Leetcode 98 验证二叉搜索树
    private List<Integer> inOrderList = new ArrayList<>();

    public boolean isValidBST(TreeNode root) {
        if (root == null) {
            return true;
        }
        inOrderTraversal(root);
        for (int i = 1; i < inOrderList.size(); i++) {
            if (inOrderList.get(i) <= inOrderList.get(i - 1)) {
                return false;
            }
        }
        return true;
    }

    private void inOrderTraversal(TreeNode root) {
        if (root == null) {
            return;
        }
        inOrderTraversal(root.left);
        inOrderList.add(root.val);
        inOrderTraversal(root.right);
    }

    public boolean isValidBST2(TreeNode root) {
        return isValidBST2(root, Long.MIN_VALUE, Long.MAX_VALUE);
    }

    private boolean isValidBST2(TreeNode root, long min, long max) {
        if (root == null) {
            return true;
        }
        if (root.val < min || root.val > max) {
            return false;
        }
        return isValidBST2(root.left, min, root.val) && isValidBST2(root.right, root.val, max);
    }

    //Leetcode 938 二叉搜索树的范围和
    public int rangeSumBST(TreeNode root, int low, int high) {
        if (root == null) return 0;
        int sum = 0;
        if (root.val >= low && root.val <= high) {
            sum = root.val + rangeSumBST(root.left, low, high) + rangeSumBST(root.right, low, high);
        } else if (root.val < low) {
            sum += rangeSumBST(root.left, low, high);

        } else {
            sum += rangeSumBST(root.right, low, high);
        }
        return sum;
    }

    public int rangeSumBST2(TreeNode root, int low, int high) {
        if (root == null) {
            return 0;
        }
        int sum = 0;
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);
        while (!stack.isEmpty()) {
            TreeNode node = stack.pop();
            if (node != null) {
                if (node.val >= low && node.val <= high) {
                    sum += node.val;
                }
                if (node.val < high) {
                    stack.push(node.right);
                }
                if (node.val > low) {
                    stack.push(node.left);
                }
            }
        }
        return sum;
    }

    //LeetCode 1008: 根据前序遍历构造二叉搜索树
    public TreeNode bstFromPreorder(int[] preorder) {
        return buildTree(preorder, 0, preorder.length - 1);
    }

    private TreeNode buildTree(int[] preorder, int start, int end) {
        if (start > end) {
            return null;
        }
        TreeNode root = new TreeNode(preorder[start]);

        int index = start + 1;
        while (index < end && preorder[index] < root.val) {
            index++;
        }
        root.left = buildTree(preorder, start + 1, index - 1);
        root.right = buildTree(preorder, index, end);
        return root;
    }

    private int index = 0;

    public TreeNode bstFromPreorder2(int[] preorder) {
        return buildTree2(preorder, Integer.MAX_VALUE);
    }

    private TreeNode buildTree2(int[] preorder, int upperBound) {
        if (index >= preorder.length || preorder[index] > upperBound) {
            return null;
        }
       TreeNode root = new TreeNode(preorder[index++]);

        root.left = buildTree2(preorder, root.val);
        root.right = buildTree2(preorder, upperBound);
        return root;
    }
    //LeetCode 235: 二叉搜索树的最近公共祖先
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if (root.val > p.val && root.val > q.val) {
            return lowestCommonAncestor(root.left, p, q);
        } else if (root.val < p.val && root.val < q.val) {
            return lowestCommonAncestor(root.right, p, q);
        } else {
            return root;
        }
    }
    public TreeNode lowestCommonAncestor2(TreeNode root, TreeNode p, TreeNode q) {
            TreeNode current = root;
            while (current!= null) {
                if (current.val > p.val && current.val > q.val) {
                    current = current.left;
                } else if (current.val < p.val && current.val < q.val) {
                    current = current.right;
                } else {
                    return current;
                }
            }
            return null;
    }


}
详细解答
以下是添加了详细注释的完整代码,解释了每个方法的核心逻辑、复杂度分析和关键点:

```java
package com.itheima.BST;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

/**
 * 二叉搜索树(BST)相关LeetCode题解
 * 包含搜索、插入、删除、验证、构造等操作
 */
public class BstLeetCode {

    // E01 450. 删除二叉搜索树中的节点
    /**
     * 删除BST中的指定节点,保持BST性质
     * 时间复杂度:O(h),h为树的高度
     * 空间复杂度:O(h),递归栈深度
     */
    public TreeNode deleteNode(TreeNode root, int key) {
        if (root == null) return null;
        
        // 递归查找要删除的节点
        if (key < root.val) {
            root.left = deleteNode(root.left, key);
        } else if (key > root.val) {
            root.right = deleteNode(root.right, key);
        } else {
            // 找到节点,处理删除逻辑
            if (root.left == null) return root.right;
            if (root.right == null) return root.left;
            
            // 有两个子节点的情况:找到右子树的最小节点(中序后继)
            TreeNode minNode = findMin(root.right);
            root.val = minNode.val; // 用后继节点值替换当前节点
            root.right = deleteNode(root.right, minNode.val); // 删除后继节点
        }
        return root;
    }

    // 查找以node为根的子树中的最小节点
    private TreeNode findMin(TreeNode node) {
        while (node.left != null) node = node.left;
        return node;
    }

    // Leetcode 701 二叉搜索树中的插入操作
    /**
     * 递归插入新节点,保持BST性质
     * 时间复杂度:O(h)
     * 空间复杂度:O(h)
     */
    public TreeNode insertIntoBST(TreeNode root, int val) {
        if (root == null) return new TreeNode(val);
        
        if (val < root.val) {
            root.left = insertIntoBST(root.left, val);
        } else {
            root.right = insertIntoBST(root.right, val);
        }
        return root;
    }

    /**
     * 迭代插入新节点,保持BST性质
     * 时间复杂度:O(h)
     * 空间复杂度:O(1)
     */
    public TreeNode insertIntoBST2(TreeNode root, int val) {
        if (root == null) return new TreeNode(val);
        
        TreeNode current = root;
        while (true) {
            if (val < current.val) {
                if (current.left == null) {
                    current.left = new TreeNode(val);
                    break;
                }
                current = current.left;
            } else {
                if (current.right == null) {
                    current.right = new TreeNode(val);
                    break;
                }
                current = current.right;
            }
        }
        return root;
    }

    // Leetcode 700 二叉搜索树中的搜索
    /**
     * 递归搜索指定值的节点
     * 时间复杂度:O(h)
     * 空间复杂度:O(h)
     */
    public TreeNode searchBST(TreeNode root, int val) {
        if (root == null || root.val == val) return root;
        return val < root.val ? searchBST(root.left, val) : searchBST(root.right, val);
    }

    /**
     * 迭代搜索指定值的节点
     * 时间复杂度:O(h)
     * 空间复杂度:O(1)
     */
    public TreeNode searchBST2(TreeNode root, int val) {
        while (root != null) {
            if (root.val == val) return root;
            root = val < root.val ? root.left : root.right;
        }
        return null;
    }

    // Leetcode 98 验证二叉搜索树
    private List<Integer> inOrderList = new ArrayList<>();

    /**
     * 中序遍历验证BST(非最优解)
     * 时间复杂度:O(n)
     * 空间复杂度:O(n)
     */
    public boolean isValidBST(TreeNode root) {
        if (root == null) return true;
        
        // 中序遍历生成有序列表
        inOrderTraversal(root);
        
        // 检查列表是否严格递增
        for (int i = 1; i < inOrderList.size(); i++) {
            if (inOrderList.get(i) <= inOrderList.get(i - 1)) {
                return false;
            }
        }
        return true;
    }

    private void inOrderTraversal(TreeNode root) {
        if (root == null) return;
        inOrderTraversal(root.left);
        inOrderList.add(root.val);
        inOrderTraversal(root.right);
    }

    /**
     * 递归边界验证BST(最优解)
     * 时间复杂度:O(n)
     * 空间复杂度:O(h)
     */
    public boolean isValidBST2(TreeNode root) {
        return isValidBST2(root, Long.MIN_VALUE, Long.MAX_VALUE);
    }

    private boolean isValidBST2(TreeNode root, long min, long max) {
        if (root == null) return true;
        
        // 当前节点值必须在(min, max)范围内
        if (root.val <= min || root.val >= max) return false;
        
        // 递归验证左右子树
        return isValidBST2(root.left, min, root.val) && 
               isValidBST2(root.right, root.val, max);
    }

    // Leetcode 938 二叉搜索树的范围和
    /**
     * 递归计算范围内节点和
     * 时间复杂度:O(n)
     * 空间复杂度:O(h)
     */
    public int rangeSumBST(TreeNode root, int low, int high) {
        if (root == null) return 0;
        
        if (root.val >= low && root.val <= high) {
            // 当前节点在范围内,累加左右子树
            return root.val + rangeSumBST(root.left, low, high) + rangeSumBST(root.right, low, high);
        } else if (root.val < low) {
            // 当前节点小于下界,只搜索右子树
            return rangeSumBST(root.right, low, high);
        } else {
            // 当前节点大于上界,只搜索左子树
            return rangeSumBST(root.left, low, high);
        }
    }

    /**
     * 迭代计算范围内节点和
     * 时间复杂度:O(n)
     * 空间复杂度:O(h)
     */
    public int rangeSumBST2(TreeNode root, int low, int high) {
        if (root == null) return 0;
        
        int sum = 0;
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);
        
        while (!stack.isEmpty()) {
            TreeNode node = stack.pop();
            if (node != null) {
                if (node.val >= low && node.val <= high) {
                    sum += node.val;
                }
                // 根据BST性质选择性入栈
                if (node.val < high) stack.push(node.right);
                if (node.val > low) stack.push(node.left);
            }
        }
        return sum;
    }

    // LeetCode 1008: 根据前序遍历构造二叉搜索树
    /**
     * 递归构造BST(方法一:划分左右子树)
     * 时间复杂度:O(n²)
     * 空间复杂度:O(h)
     */
    public TreeNode bstFromPreorder(int[] preorder) {
        return buildTree(preorder, 0, preorder.length - 1);
    }

    private TreeNode buildTree(int[] preorder, int start, int end) {
        if (start > end) return null;
        
        TreeNode root = new TreeNode(preorder[start]);
        
        // 找到第一个大于根节点的位置,划分左右子树
        int index = start + 1;
        while (index <= end && preorder[index] < root.val) {
            index++;
        }
        
        root.left = buildTree(preorder, start + 1, index - 1);
        root.right = buildTree(preorder, index, end);
        return root;
    }

    private int index = 0;

    /**
     * 递归构造BST(方法二:上界约束)
     * 时间复杂度:O(n)
     * 空间复杂度:O(h)
     */
    public TreeNode bstFromPreorder2(int[] preorder) {
        return buildTree2(preorder, Integer.MAX_VALUE);
    }

    private TreeNode buildTree2(int[] preorder, int upperBound) {
        if (index >= preorder.length || preorder[index] > upperBound) {
            return null;
        }
        
        TreeNode root = new TreeNode(preorder[index++]);
        
        // 左子树的上界是当前节点值
        root.left = buildTree2(preorder, root.val);
        // 右子树的上界是当前上界
        root.right = buildTree2(preorder, upperBound);
        
        return root;
    }

    // LeetCode 235: 二叉搜索树的最近公共祖先
    /**
     * 递归查找LCA
     * 时间复杂度:O(h)
     * 空间复杂度:O(h)
     */
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if (root.val > p.val && root.val > q.val) {
            return lowestCommonAncestor(root.left, p, q);
        } else if (root.val < p.val && root.val < q.val) {
            return lowestCommonAncestor(root.right, p, q);
        } else {
            // 当前节点即为LCA
            return root;
        }
    }

    /**
     * 迭代查找LCA
     * 时间复杂度:O(h)
     * 空间复杂度:O(1)
     */
    public TreeNode lowestCommonAncestor2(TreeNode root, TreeNode p, TreeNode q) {
        TreeNode current = root;
        while (current != null) {
            if (current.val > p.val && current.val > q.val) {
                current = current.left;
            } else if (current.val < p.val && current.val < q.val) {
                current = current.right;
            } else {
                return current;
            }
        }
        return null;
    }
}

// 二叉树节点定义
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int x) { val = x; }
}
```

### 核心方法解释

1. **删除节点(deleteNode)**
   - 三种情况处理:无子节点、一个子节点、两个子节点
   - 两个子节点时用右子树最小节点(中序后继)替换

2. **插入节点(insertIntoBST)**
   - 递归找到合适的空位置插入新节点
   - 迭代版本通过循环避免递归栈开销

3. **验证BST(isValidBST2)**
   - 最优解法通过递归传递上下界约束
   - 每个节点的值必须在 (min, max) 范围内

4. **前序构造BST(bstFromPreorder2)**
   - 时间优化版利用上界约束,O(n)时间复杂度
   - 全局索引变量跟踪当前处理的元素

5. **最近公共祖先(lowestCommonAncestor)**
   - 利用BST特性,快速定位LCA
   - 递归和迭代两种实现方式

### 复杂度总结

| 方法 | 时间复杂度 | 空间复杂度 |
|------|------------|------------|
| 删除节点 | O(h) | O(h) |
| 插入节点 | O(h) | O(h)/O(1) |
| 搜索节点 | O(h) | O(h)/O(1) |
| 验证BST | O(n) | O(n)/O(h) |
| 范围和 | O(n) | O(h)/O(h) |
| 前序构造 | O(n²)/O(n) | O(h) |
| 最近公共祖先 | O(h) | O(h)/O(1) |

这些方法充分体现了二叉搜索树的有序性特性,合理利用BST性质可以高效解决各种树操作问题。
平衡二叉树
package com.itheima.AVLTree;

public class AVLTree {
    static class Node {
        int key;
        int height;
        Node left, right;

        public Node(int key) {
            this.key = key;
            this.height = 1;
        }
    }

    private Node root;

    private int height(Node node) {
        return node == null ? 0 : node.height;
    }

    private int getBalance(Node node) {
        return node == null ? 0 : height(node.left) - height(node.right);
    }

    private Node rightRotate(Node y) {
        Node x = y.left;
        Node T2 = x.right;
        x.right = y;
        y.left = T2;
        y.height = Math.max(height(y.left), height(y.right)) + 1;
        x.height = Math.max(height(x.left), height(x.right)) + 1;
        return x;
    }

    private Node leftRotate(Node x) {
        Node y = x.right;
        Node T2 = y.left;
        y.left = x;
        x.right = T2;
        x.height = Math.max(height(x.left), height(x.right)) + 1;
        y.height = Math.max(height(y.left), height(y.right)) + 1;
        return y;
    }

    public void insert(int key) {
        root = insert(root, key);
    }

    private Node insert(Node node, int key) {
        if (node == null) {
            return new Node(key);
        }
        if (key < node.key) {
            node.left = insert(node.left, key);
        } else if (key > node.key) {
            node.right = insert(node.right, key);
        } else {
            return node;
        }
        node.height = Math.max(height(node.left), height(node.right)) + 1;

        int balance = getBalance(node);
        if (balance > 1 && key < node.left.key) {
            return rightRotate(node);
        }
        if (balance < -1 && key > node.right.key) {
            return leftRotate(node);
        }
        if (balance > 1 && key > node.left.key) {
            node.left = leftRotate(node.left);
            return rightRotate(node);
        }
        if (balance < -1 && key < node.right.key) {
            node.right = rightRotate(node.right);
            return leftRotate(node);
        }
        return node;
    }

    private Node minValueNode(Node node) {
        Node current = node;
        while (current.left != null) {
            current = current.left;
        }
        return current;
    }

    public void delete(int key) {
        root = delete(root, key);
    }

    private Node delete(Node root, int key) {
        if (root == null) {
            return root;
        }
        if (key < root.key) {
            root.left = delete(root.left, key);
        } else if (key > root.key) {
            root.right = delete(root.right, key);
        } else {
            if (root.left == null || root.right == null) {
                Node temp = root.left != null ? root.left : root.right;
            } else {
                Node temp = minValueNode(root.right);
                root.key = temp.key;
                root.right = delete(root.right, temp.key);
            }
            if (root == null) {
                return root;
            }
            root.height = Math.max(height(root.left), height(root.right)) + 1;
            int balance = getBalance(root);
            if (balance > 1 && key < root.left.key) {
                return rightRotate(root);
            }

            if (balance < -1 && key > root.right.key) {
                return leftRotate(root);
            }
            if (balance > 1 && key > root.left.key) {
                root.left = leftRotate(root.left);
                return rightRotate(root);
            }
            if (balance < -1 && key < root.right.key) {
                root.right = rightRotate(root.right);
                return leftRotate(root);
            }
        }
        return root;
    }
}
注释
以下是添加了更详细注释的AVL树实现代码,包含每个方法的算法思路、关键点、复杂度分析和示例说明:

```java
package com.itheima.tree;

/**
 * AVL树实现示例
 * 自平衡二叉搜索树,通过旋转操作保持树的平衡,确保所有操作的时间复杂度为O(log n)
 */
public class AVLTree {

    // AVL树节点定义
    static class Node {
        int key;           // 节点键值,用于比较和排序
        int height;        // 节点高度,用于计算平衡因子
        Node left, right;  // 左右子节点引用

        public Node(int key) {
            this.key = key;
            this.height = 1;  // 新节点高度默认为1(叶子节点)
        }
    }

    private Node root;  // 根节点

    // 获取节点高度
    private int height(Node node) {
        return node == null ? 0 : node.height;  // 空节点高度为0
    }

    // 获取平衡因子:左子树高度 - 右子树高度
    // 平衡因子范围应为[-1, 1],否则需要调整
    private int getBalance(Node node) {
        return node == null ? 0 : height(node.left) - height(node.right);
    }

    // 右旋操作(RR旋转)
    // 用于修复LL型不平衡(左左情况)
    // 示例:
    //       y                          x
    //      / \                        / \
    //     x   T3     右旋(y)        z   y
    //    / \       --------->      /|  / \
    //   z  T2                    T1 T2 T3
    //  /
    // T1
    private Node rightRotate(Node y) {
        Node x = y.left;
        Node T2 = x.right;

        // 执行旋转:将x提升为根节点,y变为x的右子节点
        x.right = y;
        y.left = T2;

        // 更新高度:先更新子节点y,再更新父节点x
        y.height = Math.max(height(y.left), height(y.right)) + 1;
        x.height = Math.max(height(x.left), height(x.right)) + 1;

        // 返回新的根节点
        return x;
    }

    // 左旋操作(LL旋转)
    // 用于修复RR型不平衡(右右情况)
    // 示例:
    //       x                          y
    //      / \                        / \
    //     T1  y     左旋(x)         x   z
    //        / \    --------->     / \  |\
    //       T2  z                T1 T2 T3
    //          |
    //          T3
    private Node leftRotate(Node x) {
        Node y = x.right;
        Node T2 = y.left;

        // 执行旋转:将y提升为根节点,x变为y的左子节点
        y.left = x;
        x.right = T2;

        // 更新高度:先更新子节点x,再更新父节点y
        x.height = Math.max(height(x.left), height(x.right)) + 1;
        y.height = Math.max(height(y.left), height(y.right)) + 1;

        // 返回新的根节点
        return y;
    }

    // 插入操作
    public void insert(int key) {
        root = insert(root, key);
    }

    // 递归插入新节点,并在插入后调整平衡
    private Node insert(Node node, int key) {
        // 1. 执行标准BST插入
        if (node == null)
            return new Node(key);  // 找到插入位置,创建新节点

        if (key < node.key)
            node.left = insert(node.left, key);
        else if (key > node.key)
            node.right = insert(node.right, key);
        else // 不允许重复键
            return node;

        // 2. 更新当前节点的高度:子树高度最大值+1
        node.height = 1 + Math.max(height(node.left), height(node.right));

        // 3. 获取平衡因子,检查是否失衡
        int balance = getBalance(node);

        // 4. 如果失衡,执行四种旋转之一

        // LL型:左左情况 -> 右旋
        // 失衡条件:balance > 1 且 新节点插入到左子树的左子树中
        if (balance > 1 && key < node.left.key)
            return rightRotate(node);

        // RR型:右右情况 -> 左旋
        // 失衡条件:balance < -1 且 新节点插入到右子树的右子树中
        if (balance < -1 && key > node.right.key)
            return leftRotate(node);

        // LR型:左右情况 -> 先左旋后右旋
        // 失衡条件:balance > 1 且 新节点插入到左子树的右子树中
        if (balance > 1 && key > node.left.key) {
            node.left = leftRotate(node.left);  // 先对左子树进行左旋
            return rightRotate(node);           // 再对当前节点进行右旋
        }

        // RL型:右左情况 -> 先右旋后左旋
        // 失衡条件:balance < -1 且 新节点插入到右子树的左子树中
        if (balance < -1 && key < node.right.key) {
            node.right = rightRotate(node.right);  // 先对右子树进行右旋
            return leftRotate(node);               // 再对当前节点进行左旋
        }

        // 未失衡,返回未修改的节点指针
        return node;
    }

    // 查找最小节点:辅助方法,用于删除有两个子节点的情况
    private Node minValueNode(Node node) {
        Node current = node;
        // 循环查找左子树的最左节点(最小值)
        while (current.left != null)
            current = current.left;
        return current;
    }

    // 删除操作
    public void delete(int key) {
        root = delete(root, key);
    }

    // 递归删除节点,并在删除后调整平衡
    private Node delete(Node root, int key) {
        // 1. 执行标准BST删除
        if (root == null)
            return root;

        if (key < root.key)
            root.left = delete(root.left, key);
        else if (key > root.key)
            root.right = delete(root.right, key);
        else {
            // 找到要删除的节点

            // 情况1:只有一个子节点或无子节点
            if (root.left == null || root.right == null) {
                root = (root.left != null) ? root.left : root.right;
            }
            // 情况2:有两个子节点
            else {
                // 获取右子树的最小节点(中序后继)
                Node temp = minValueNode(root.right);
                root.key = temp.key;  // 用后继节点值替换当前节点
                root.right = delete(root.right, temp.key);  // 删除后继节点
            }

            // 如果树只有一个节点,直接返回
            if (root == null)
                return root;
        }

        // 2. 更新当前节点的高度
        root.height = 1 + Math.max(height(root.left), height(root.right));

        // 3. 获取平衡因子,检查是否失衡
        int balance = getBalance(root);

        // 4. 如果失衡,执行四种旋转之一

        // LL型:左子树过高,且左子树平衡因子≥0
        if (balance > 1 && getBalance(root.left) >= 0)
            return rightRotate(root);

        // LR型:左子树过高,且左子树平衡因子<0
        if (balance > 1 && getBalance(root.left) < 0) {
            root.left = leftRotate(root.left);  // 先左旋左子树
            return rightRotate(root);           // 再右旋当前节点
        }

        // RR型:右子树过高,且右子树平衡因子≤0
        if (balance < -1 && getBalance(root.right) <= 0)
            return leftRotate(root);

        // RL型:右子树过高,且右子树平衡因子>0
        if (balance < -1 && getBalance(root.right) > 0) {
            root.right = rightRotate(root.right);  // 先右旋右子树
            return leftRotate(root);               // 再左旋当前节点
        }

        return root;
    }

    // 中序遍历:按升序输出节点
    public void inorder() {
        inorder(root);
        System.out.println();
    }

    private void inorder(Node root) {
        if (root != null) {
            inorder(root.left);
            System.out.print(root.key + " ");
            inorder(root.right);
        }
    }

    // 前序遍历:按根-左-右顺序输出,用于展示树的结构
    public void preorder() {
        preorder(root);
        System.out.println();
    }

    private void preorder(Node root) {
        if (root != null) {
            System.out.print("[" + root.key + " h:" + root.height + "] ");
            preorder(root.left);
            preorder(root.right);
        }
    }

    // 测试示例
    public static void main(String[] args) {
        AVLTree tree = new AVLTree();

        // 插入测试数据
        tree.insert(9);
        tree.insert(5);
        tree.insert(10);
        tree.insert(0);
        tree.insert(6);
        tree.insert(11);
        tree.insert(-1);
        tree.insert(1);
        tree.insert(2);

        /* 插入后的树结构(高度标注):
                9(h:4)
               /     \
          1(h:3)    10(h:2)
           /   \         \
      0(h:2)  5(h:2)     11(h:1)
       /     /   \
    -1(h:1) 2(h:1) 6(h:1)
    */

        System.out.println("插入后的中序遍历(升序):");
        tree.inorder();  // 输出: -1 0 1 2 5 6 9 10 11

        System.out.println("插入后的前序遍历(带高度信息):");
        tree.preorder(); // 输出: [9 h:4] [1 h:3] [0 h:2] [-1 h:1] [5 h:2] [2 h:1] [6 h:1] [10 h:2] [11 h:1]

        // 删除测试:删除10,触发RL旋转
        tree.delete(10);

        /* 删除10后的树结构:
                1(h:4)
               /     \
          0(h:2)    9(h:3)
           /       /   \
        -1(h:1)  5(h:2) 11(h:1)
               /   \
            2(h:1) 6(h:1)
    */

        System.out.println("删除10后的中序遍历:");
        tree.inorder();  // 输出: -1 0 1 2 5 6 9 11

        System.out.println("删除10后的前序遍历:");
        tree.preorder(); // 输出: [1 h:4] [0 h:2] [-1 h:1] [9 h:3] [5 h:2] [2 h:1] [6 h:1] [11 h:1]
    }
}
```

### 核心算法详解

1. **平衡因子与失衡条件**:
   - 平衡因子 = 左子树高度 - 右子树高度
   - 平衡条件:平衡因子 ∈ [-1, 1]
   - 失衡条件:平衡因子 > 1 或 < -1

2. **四种旋转操作**:
   - **LL旋转(右旋)**:修复左左型失衡(插入点在左子树的左子树)
   - **RR旋转(左旋)**:修复右右型失衡(插入点在右子树的右子树)
   - **LR旋转(先左旋后右旋)**:修复左右型失衡(插入点在左子树的右子树)
   - **RL旋转(先右旋后左旋)**:修复右左型失衡(插入点在右子树的左子树)

3. **插入操作步骤**:
   1. 执行标准BST插入
   2. 回溯更新节点高度
   3. 计算平衡因子
   4. 根据失衡类型执行对应旋转

4. **删除操作步骤**:
   1. 执行标准BST删除(找到并删除节点,用中序后继替换)
   2. 回溯更新节点高度
   3. 计算平衡因子
   4. 根据失衡类型执行对应旋转

### 复杂度分析

| 操作 | 时间复杂度 | 空间复杂度 | 关键说明 |
|------|------------|------------|----------|
| 插入 | O(log n)   | O(log n)   | 递归栈深度为树高 |
| 删除 | O(log n)   | O(log n)   | 递归栈深度为树高 |
| 查找 | O(log n)   | O(log n)   | 递归栈深度为树高 |

AVL树通过严格保持平衡(每个节点的平衡因子绝对值不超过1),确保树的高度始终为O(log n),从而保证所有操作的高效性。

### 测试用例说明

测试数据插入顺序:9 → 5 → 10 → 0 → 6 → 11 → -1 → 1 → 2

1. **插入过程中的平衡调整**:
   - 插入9:根节点
   - 插入5:9的左子节点
   - 插入10:9的右子节点
   - 插入0:5的左子节点,触发LL旋转,根节点变为5
   - 插入6:10的左子节点
   - 插入11:10的右子节点
   - 插入-1:0的左子节点
   - 插入1:0的右子节点
   - 插入2:1的右子节点,触发LR旋转

2. **删除过程中的平衡调整**:
   - 删除10:触发RL旋转,根节点变为1

通过详细的注释和测试用例,这个AVL树实现可以帮助理解自平衡二叉搜索树的核心原理和实现细节。

技术栈
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值