剑指offer - 树
1.重建二叉树
问题描述:
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
/* function TreeNode(x) {
this.val = x;
this.left = null;
this.right = null;
} */
function reConstructBinaryTree(pre, vin) {
// write code here
if (pre.length == 0 || vin.length == 0) {
return null;
}
var root = pre[0]; //根节点
var index = vin.indexOf(root); //在vin中找到根节点的索引
var left = vin.slice(0, index); //左子树
var right = vin.slice(index + 1); //右子树
var node = new TreeNode(root); //新建一个二叉树
node.left = reConstructBinaryTree(pre.slice(1, index + 1), left); //左子树的前序和中序
node.right = reConstructBinaryTree(pre.slice(index + 1), right); //右子树的前序和中序
return node;
}
2.二叉树的下一个结点
给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
解题思路:
分析二叉树的下一个节点,一共有以下情况:
1.二叉树为空,则返回空;
2.有右子树的,那么下个结点就是右子树最左边的点
3.没有右子树的,也可以分成两类:
- 是父节点左孩子,那么父节点就是下一个节点
- 是父节点的右孩子,找他的父节点的父节点的父节点...直到当前结点是其父节点的左孩子位置,下一个结点就是当前结点
/*function TreeLinkNode(x){
this.val = x;
this.left = null;
this.right = null;
this.next = null;
}*/
function GetNext(pNode) {
// write code here
if (pNode === null) return null; //空结点
var p = null;
if (pNode.right) {
//有右子树,则下一个结点在右子树最左边的结点
p = pNode.right;
while (p.left !== null) {
p = p.left;
}
return p;
} else {
//没有右子树
p = pNode.next;
if (p && p.right === pNode) {
while (p.next && p.next.right === p) {
p = p.next;
}
p = p.next;
}
return p;
}
return null;
}
3.对称的二叉树
问题描述:
请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。空二叉树也是对称的。
解题思路:
空二叉树也是对称的,这是值得注意的点。
然后将二叉树一层一层比较一下
/* function TreeNode(x) {
this.val = x;
this.left = null;
this.right = null;
} */
function isSymmetrical(pRoot) {
// write code here
if (!pRoot) return true; // 注意是true
return judge(pRoot.left, pRoot.right);
}
function judge(node1, node2) {
//判断这两个结点以及子节点是否对称
if (node1 === null && node2 === null) {
return true;
} else if (node1 === null || node2 === null) {
return false;
}
if (node1.val !== node2.val) {
return false;
} else {
return judge(node1.left, node2.right) && judge(node1.right, node2.left);
}
}
4.按之字形顺序打印二叉树
问题描述:
请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。空二叉树输出空数组。
输入:
输出:
[[8],[10,6],[5,7,9,11]]
/* function TreeNode(x) {
this.val = x;
this.left = null;
this.right = null;
*/
function Print(pRoot) {
// write code here
if (!pRoot) return [];
const nodes = []; //结点
const vals = []; //val
var flag = true; //true代表顺序打印
nodes.push(pRoot);
while (nodes.length) {
var temp = [];
var len = nodes.length; //这里注意要先把nodes的长度赋值给一个变量,因为下面循环中nodes里会添加新的结点
for (let i = 0; i < len; i++) {
var node = nodes.shift(); //每次弹出nodes中第一个结点
flag === true ? temp.push(node.val) : temp.unshift(node.val);
if (node.left) {
nodes.push(node.left);
}
if (node.right) {
nodes.push(node.right);
}
}
flag = !flag;
vals.push(temp);
}
return vals;
}
5.把二叉树打印成多行
问题描述:
从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
输入:
输出:
[[8],[6,10],[5,7,9,11]]
/* function TreeNode(x) {
this.val = x;
this.left = null;
this.right = null;
} */
function Print(pRoot) {
// write code here'
if (!pRoot) return [];
const nodes = [];
const vals = [];
nodes.push(pRoot);
while (nodes.length) {
var len = nodes.length;
var temp = [];
for (let i = 0; i < len; i++) {
var node = nodes.shift();
temp.push(node.val);
if (node.left) {
nodes.push(node.left);
}
if (node.right) {
nodes.push(node.right);
}
}
vals.push(temp);
}
return vals;
}
6.序列化二叉树
问题描述:
请实现两个函数,分别用来序列化和反序列化二叉树
二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#),以 ! 表示一个结点值的结束(value!)。
二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果 str,重构二叉树。
/* function TreeNode(x) {
this.val = x;
this.left = null;
this.right = null;
} */
var arr = [];
function Serialize(pRoot) {
// write code here
if (!pRoot) {
arr.push("#");
} else {
arr.push(pRoot.val);
Serialize(pRoot.left);
Serialize(pRoot.right);
}
}
function Deserialize(s) {
// write code here
if (arr === null) return null;
if (arr.length < 1) return null;
var root = null;
var temp = arr.shift();
if (typeof temp === "number") {
root = new TreeNode(temp);
root.left = Deserialize(arr);
root.right = Deserialize(arr);
}
return root;
}
7.二叉搜索树的第 k 个结点
题目描述:
给定一棵二叉搜索树,请找出其中的第 k 小的结点。例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为 4。
解题思路:
二叉搜索树的特性:左子树 < 根节点 < 右子树
故只需要找出二叉搜索树的中序,然后找第 k 个结点
/* function TreeNode(x) {
this.val = x;
this.left = null;
this.right = null;
} */
function KthNode(pRoot, k) {
// write code here
var arr = []; //中序存储结点
// 中序函数
function mid(pRoot) {
if (!pRoot) return null;
mid(pRoot.left);
arr.push(pRoot);
mid(pRoot.right);
}
// 调用中序函数
mid(pRoot);
return arr[k - 1];
}
8.数据流中的中位数
问题描述:
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用 Insert()方法读取数据流,使用 GetMedian()方法获取当前读取数据的中位数。
var arr = [];
function Insert(num) {
// write code here
arr.push(num);
var i = arr.length - 1;
while (i > 0) {
if (arr[i] < arr[i - 1]) {
[arr[i], arr[i - 1]] = [arr[i - 1], arr[i]];
}
i--;
}
return arr;
}
function GetMedian() {
// write code here
if (!arr.length) return null;
var length = arr.length;
var mid = parseInt(length / 2);
if (length % 2 === 0) {
var avg = (arr[mid - 1] + arr[mid]) / 2;
return avg;
} else {
return arr[mid];
}
}