一天一大 lee(恢复二叉搜索树)难度:困难-Day20200808
时间:2022-07-25
本文章向大家介绍一天一大 lee(恢复二叉搜索树)难度:困难-Day20200808,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
题目:
二叉搜索树中的两个节点被错误地交换。
请在不改变其结构的情况下,恢复这棵树。
示例
- 示例 1
输入: [1,3,null,null,2]
1
/
3
2
输出: [3,1,null,null,2]
3
/
1
2
- 示例 2
输入: [3,1,4,null,null,2]
3
/
1 4
/
2
输出: [2,1,4,null,null,3]
2
/
1 4
/
3
抛砖引玉
抛砖引玉
思路
二叉搜索树(二叉查找树,二叉排序树):
- 空
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉搜索树
中序遍历
- 首先遍历左子树,然后访问根结点,最后遍历右子树
- recover(root.left)
- root
- recover(root.right)
中序遍历二叉搜索树得到节点应该是递增的,存在位置错误则一定存在非递增节点
- 存在两个非递增节点 i,j:交换 i 与 j
- 存在一个非递增节点 i:交换 i 与 i+1
显式中序遍历
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {void} Do not return anything, modify root in-place instead.
*/
var recoverTree = function (root) {
let nums = []
inorder(root, nums)
let [first, second] = findTwoSwapped(nums)
// 默认替换两个节点,如只需替换一个传入的替换元素为-1,则查找不到
recover(root, 2, first, second)
// 中序遍历
function inorder(root, nums) {
if (root === null) return
// 先遍历左节点
inorder(root.left, nums)
// 再遍历根节点
nums.push(root.val)
// 最后遍历有节点
inorder(root.right, nums)
}
// 循环中序遍历得到的节点,查找非递增节点
function findTwoSwapped(nums) {
const n = nums.length
let x = -1,
y = -1
for (let i = 0; i < n - 1; ++i) {
// 存在非递增节点,则返回
if (nums[i + 1] < nums[i]) {
y = nums[i + 1]
if (x === -1) {
x = nums[i]
} else {
// 找到两个节点终止查找
break
}
}
}
return [x, y]
}
// 遍历二叉树替换查找的非递增节点
function recover(node, count, x, y) {
if (node !== null) {
if (node.val === x || node.val === y) {
node.val = node.val === x ? y : x
if (--count === 0) return
recover(node.left, count, x, y)
recover(node.right, count, x, y)
}
}
}
}
隐式中序遍历
- 在遍历时就记录要替换的 x,y
var recoverTree = function (root) {
let stack = [],
x = null,
y = null,
pred = null
// 隐式中序遍历
while (stack.length || root !== null) {
// 先循环左节点
while (root !== null) {
stack.push(root)
root = root.left
}
// 取出最后一个节点与上一个节点比较 a vs [....←]
root = stack.pop()
// 存在非递增节点,则存放到x,y中
if (pred !== null && root.val < pred.val) {
y = root
if (x === null) {
x = pred
} else {
// 找到两个节点终止查找
break
}
}
// 本轮最后一个节点存放到上一个节点位置
pred = root
// 在循环右侧节点
root = root.right
}
swap(x, y)
// 交换节点
function swap(x, y) {
const temp = x.val
x.val = y.val
y.val = temp
}
}
Morris 中序遍历
查找当前节点的前一个节点 node
- 任意一个节点假设其为根节点 A
- 那中序遍历时根据:左子树->根节点->右子树
- 根节点 A 的前一个节点就一定来自左子树
- 于是选左子树的一个节点假设为根节点 B
- 就可以发现 A 的前一个节点可能来着 B 的左子树 B-left,或者右子树 B-right
- 如果 B-left 为空,则 node 应该在 B-right 上,B-right 上的叶子节点或者其本身
- 如果 B-left 不为空:则 node 为 B-left 的最后一个叶子节点
var recoverTree = function (root) {
let x = null,
y = null,
pred = null,
node = null
while (root !== null) {
// 假设当前节点为根节点,找到中序遍历时他的前一个节点
// 存在左子树则前一个节点应该在其左子树上的最右叶子节点
if (root.left !== null) {
node = root.left
// 找当前二叉树左子树的最后一个叶子节点
while (node.right !== null && node.right !== root) {
node = node.right
}
if (node.right === null) {
// 拼接上衔接该整改左子树的上一个节点
node.right = root
// 拼接上来节点就成了下一个'当前节点',其就是下一个需要遍历的对象,对其执行同样的操作
root = root.left
} else {
// '当前节点'不存在左子树遍历完成,则这个节点就是上一个根节点的前一个节点
// 比较他们的大小查看是否满足递增,如果不是则需要替换他们
if (pred !== null && root.val < pred.val) {
y = root
if (x === null) {
x = pred
}
}
// 记录当前节点上一个节点
pred = root
// 复原指向
node.right = null
root = root.right
}
} else {
// 如果没有左子树,则在右子树中遇到当前节点的中序遍历的前一个叶子节点
// 比较他们的大小查看是否满足递增,如果不是则需要替换他们
if (pred !== null && root.val < pred.val) {
y = root
if (x === null) {
x = pred
}
}
// 记录当前节点上一个节点
pred = root
// 复原指向
root = root.right
}
}
swap(x, y)
// 交换节点
function swap(x, y) {
const temp = x.val
x.val = y.val
y.val = temp
}
}
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 如何优雅地删除 Linux 中的垃圾文件的方法
- Ubuntu18.04 安装 Anaconda3的教程详解
- VScode Remote SSH通过远程编辑与调试代码
- Ubuntu18.04下安装配置SSH服务的方法步骤
- Openssl实现双向认证教程(附服务端客户端代码)
- centos8使用Docker部署Django项目的详细教程
- ubuntu18.04 安装qt5.12.8及环境配置的详细教程
- 安装Ubuntu20.04与安装NVIDIA驱动的教程
- Ubuntu下安装nvidia显卡驱动(安装方式简单)
- Ubuntu 20.04 apt 更换国内源的实现方法
- Android设计模式之单例模式解析
- Android屏蔽软键盘并且显示光标的实例详解
- Android实现底部缓慢弹出菜单
- Ubuntu20的tzselect设置时间失效的问题,树莓派服务器(推荐)
- 安装Ubuntu 20.04后要做的事(小白教程)