❗❗必見の体験
ブロガーのブラッシングの質問の間、基本的にはバイナリツリーに遭遇してもバイナリツリーに遭遇しません。時には午後に質問に参加することもあります。他の人の問題を解決するという考えを理解できたとしても、自分で書いてください。私はそれを一度にブラッシングせず、代わりに最初にバイナリツリーの基本的なアルゴリズムを理解しました。次に、ソードオファーでバイナリツリーに関するすべての質問を選びました。少なくなるほど、崩壊の端まで自分を虐待することになります。 。言うまでもなく、これはさわやかです。今まで基本的な準備が無かったのも不思議ではないので、直接質問バンクに行ってチャレンジしました。
ここで私が言いたいのは、質問をブラッシングする前に、知識の予備力がなければならないということです。たとえば、初期データ構造がそれを理解できなければならない、または基本的なデータ構造にいくつかの重要な点があります。私のようなことを準備しないでください。質問をブラッシングするほど、人生を疑うほど、すべての質問が打撃になります。バイナリツリーの走査を行うと、走査の再帰的な結果がどのように現れるかさえわかりません。このアルゴリズムがバックアップされていても、まだ理解できていません。コードは3行しかないのです。ただ理解してもらえませんか?質問をブラッシングするプロセスにおいて、多くの質問は、トラバーサルが焦点である3行のコードです。
したがって、バイナリツリーの最初のステップは、基本的なアルゴリズムを紙に描くことから始めることです。再帰的な場合は、終了条件から1つずつ戻って、バイナリツリーとスタックの関係、および再帰なしのスタックを理解します。ポータル-バイナリツリーの基本的なアルゴリズム
ディレクトリ
- ✔質問4:バイナリツリーの再構築
- ✔質問17:ツリーの部分構造
- ✔質問18:二分木の鏡像
- ✔質問22:バイナリツリーを上から下に印刷します
- ✔質問23:バイナリサーチツリーのポストオーダートラバーサルシーケンス
- ✔質問24:二分木の中和は特定の値を持つパスです
- ✔質問26:二分探索木と二重にリンクされたリスト
- ✔質問38:二分木の深さ
- ✔質問39:平衡二分木
- ✔質問57:二分木の次のノード
- ✔質問58:対称二分木
- ✔質問59:ジグザグの順序でバイナリツリーを印刷します
- ✔質問60:バイナリツリーを複数行に出力します
- ✔質問61:バイナリツリーをシリアル化する
- ✔質問62:二分探索木のk番目に小さいノード
二分木構造:
function TreeNode(x) {
this.val = x;
this.left = null;
this.right = null;
}
質問4:バイナリツリーの再構築
難易度:♡♡
プリミドルオーダー
//pre:[1, 2, 4, 7, 3, 5, 6, 8]
//vin: [4, 7, 2, 1, 5, 3, 8, 6]
function reConstructBinaryTree(pre, vin) {
let tree = null
if (pre.length > 1) {
const root = pre.shift() //从前序遍历头中取出一个的父节点
const index = vin.indexOf(root) //父节点位于中序遍历中的位置
tree = new TreeNode(root)
tree.left = reConstructBinaryTree(pre.slice(0, index), vin.slice(0, index)) //递归父节点左边的节点
tree.right = reConstructBinaryTree(pre.slice(index), vin.slice(index + 1)) //递归父节点右边的节点
} else if (pre.length === 1) {
tree = new TreeNode(pre[0])
}
return tree
}
ポストミドル
//post:[7, 4, 2, 5, 8, 6, 3, 1]
//vin: [4, 7, 2, 1, 5, 3, 8, 6]
function reConstructBinaryTree(post, vin) {
let tree = null
if (post.length > 1) {
const root = post.pop() //从后序遍历尾中取出一个的父节点
const index = vin.indexOf(root) //父节点位于中序遍历中的位置
tree = new TreeNode(root)
tree.left = reConstructBinaryTree(post.slice(0, index), vin.slice(0, index)) //递归父节点左边的节点
tree.right = reConstructBinaryTree(post.slice(index), vin.slice(index + 1)) //递归父节点右边的节点
} else if (post.length == 1) {
tree = new TreeNode(post[0])
}
return tree
}
質問17:ツリーの部分構造
難易度:♡♡♡♡
下部構造
タイトル:2つのバイナリツリーAとBを入力して、BがAのサブ構造かどうかを判断します。(追記:空のツリーはツリーの下位構造ではないことに同意します)
アイデア:このDoesTreeHaveTree
関数は、プレオーダートラバーサルの再帰に少し似ています。親ノードの値の比較を取得し、等しい場合は、左ノードと右ノードの値を比較します。
function HasSubtree(pRoot1, pRoot2) {
let result = false
if (pRoot1 != null && pRoot2 != null) {
if (pRoot1.val == pRoot2.val) { //判断父节点
result = DoesTreeHaveTree(pRoot1, pRoot2)
}
if (!result) {//父节点不满足,看看它左节点是否满足
result = HasSubtree(pRoot1.left, pRoot2)
}
if (!result) {//左节点不满足,从其右节点是否满足
result = HasSubtree(pRoot1.right, pRoot2)
}
}
return result
}
function DoesTreeHaveTree(pRoot1, pRoot2) {
if (pRoot2 == null) { //root2比到底了,则一定是子结构
return true
}
if (pRoot1 == null) { //root2还没比完,root1就到底了,则一定不是子结构
return false
}
if (pRoot1.val != pRoot2.val) { //节点值不相等
return false
}
//节点值相等,继续比较它们的左右节点值是否相等
return DoesTreeHaveTree(pRoot1.left, pRoot2.left) && DoesTreeHaveTree(pRoot1.right, pRoot2.right)
}
3本の木を反転させる
元のタイトル:バックル572。別の木のサブツリー
function HasSubtree(pRoot1, pRoot2) {
let result = false
if (pRoot1 != null && pRoot2 != null) {
if (pRoot1.val == pRoot2.val) { //判断父节点
result = DoesTreeHaveTree(pRoot1, pRoot2)
}
if (!result) {
result = HasSubtree(pRoot1.left, pRoot2)
}
if (!result) {
result = HasSubtree(pRoot1.right, pRoot2)
}
}
return result
}
function DoesTreeHaveTree(pRoot1, pRoot2) {
//同时到达底部null,才是子树
if (!pRoot2 && !pRoot1) {
return true
}
//此时已经排除了两者都为null的情况,只要有一个为null则不是
if (!pRoot2 || !pRoot1) {
return false
}
//没到达底部的时候,没有一个为null
if (pRoot1.val != pRoot2.val) {
return false
}
//节点值相等,继续比较它们的左右节点值是否相等
return DoesTreeHaveTree(pRoot1.left, pRoot2.left) && DoesTreeHaveTree(pRoot1.right, pRoot2.right)
}
質問18:二分木の鏡像
難易度:♡♡
アイデア:順序トラバーサル、ノードの現在のラウンドの左ノードと右ノードを毎回交換する
function Mirror(root) {
if (root === null) {
return
}
const temp = root.left
root.left = root.right
root.right = temp
Mirror(root.left)
Mirror(root.right)
}
質問22:バイナリツリーを上から下に印刷します
難易度:♡♡♡♡♡
アイデア:バイナリツリーの階層トラバーサル(幅優先トラバーサル、キューを使用できます)
function PrintFromTopToBottom(root) {
// write code here
let tempTree = []
let rs = []
if (root) tempTree.push(root)
while (tempTree.length) {
root = tempTree.shift()
rs.push(root.val)
if (root.left) tempTree.push(root.left)
if (root.right) tempTree.push(root.right)
}
return rs
}
質問23:二分探索木のポストオーダ走査シーケンス
難易度:♡♡♡♡
問題:整数の配列を入力して、配列が二分探索木をたどった結果であるかどうかを判別します。はいの場合は「はい」、それ以外の場合は「いいえ」を出力します。入力配列内の2つの数値が互いに異なると想定します。
アイデア:法律を見つけてください。ポストオーダートラバーサルの最後のノードはルートノードであり、配列はルートノード値より小さい部分とルートノードより大きい部分に分割できます。その後、再帰的に。例:(3 6 5) (9) 7
重要なのは再帰の終了条件です。最初はsequence.length <= 1
、[6、5、9、7]のように、配列の左または右の部分が空であることを無視して、1で十分であると思いました。] [6,5]に再帰的である場合、左は[]、右は[6]です。
//sequence:[3, 6, 5, 9, 7]
//sequence:[6, 5, 9, 7]
//sequence:[3, 6, 4, 5, 9, 7]
function VerifySquenceOfBST(sequence) {
if (sequence.length) {
return helpVerify(sequence)
}
return false
}
function helpVerify(sequence) {
if (sequence.length <= 1) {//此条件下,递归结束。
return true
}
let index = 0
const key = sequence[sequence.length - 1] //后序遍历最后一个是根节点
while (sequence[index] < key) { //在数组中查找比根节点小和比根节点大的分界点
index++
}
const pos = index //记录分界点,此时分界点左边全是小于根节点值的
while (sequence[index] > key) { //判断根节点右边是否全部大于根节点值
index++
}
if (index != (sequence.length - 1)) { //接while
return false
}
//现在有左右两个部分,递归执行
return helpVerify(sequence.slice(0, pos)) && helpVerify(sequence.slice(pos, sequence.length - 1))
}
質問24:二分木は特定の値を持つパスです
難易度:♡♡♡♡
問題:ルートノードとバイナリツリーの整数を入力し、バイナリツリーのノード値の合計が入力整数であるすべてのパスを出力します。パスは、ツリーのルートノードから始まり、リーフノードに至るパスとして定義されます。(注:戻り値のリストでは、配列の長さが最大の配列が一番上にあります)
思考のトレイン:刻々と変化する宗派横断
function FindPath(root, expectNumber) {
// write code here
let result = [] //存放所有满足条件的路径
if (root) {
let path = [] //记录当前路径,当当前路劲满足条件的时候,push进result,
let currentSum = 0 //记录当前路径的和
isPath(root, expectNumber, path, result, currentSum)
}
return result
}
function isPath(root, expectNumber, path, result, currentSum) {
currentSum += root.val
path.push(root.val)
if (currentSum == expectNumber && !root.left && !root.right) { //根结点开始往下一直到叶结点,当前sum等于目标数
result.push(path.slice(0)) //注意:这里不能直接push(path),数组是引用类型。也可ES6用法:push([...path])
}
if (root.left) { //当前root有左节点
isPath(root.left, expectNumber, path, result, currentSum)
}
if (root.right) { //当前root有右节点
isPath(root.right, expectNumber, path, result, currentSum)
}
// 走到底(叶子)了,无论当前路径满不满足条件,都要回退到父节点继续搜索
path.pop()
}
推論
ツリーのルートノードからリーフノードまでではなく、任意のパスがある場合はどうなりますか?
参照サブツリーとサブ構造
質問26:二分探索木と二重にリンクされたリスト
難易度:♡♡♡
アイデア:ポイントは、ポインタpを使用して前のノードを記録することです。絵を描くことは理解しやすいです。まだトラバースの順番で
function Convert(pRootOfTree) {
if (!pRootOfTree) return null
let p = null //指针,记录前一个结点
p = ConvertSub(pRootOfTree, p)
let re = p
while (re.left) {
re = re.left
}
return re
}
function ConvertSub(pNode, p) {
if (pNode.left) p = ConvertSub(pNode.left, p);
if (p == null) {
p = pNode //找到最左端
} else {
p.right = pNode
pNode.left = p
p = pNode
}
if (pNode.right) p = ConvertSub(pNode.right, p);
return p
}
質問38:二分木の深さ
難易度:♡♡
ツリーの深さは、ルートノード(深さは1)から始まり、上から下にレイヤーごとに累積されます。高さはリーフノード(高さは1)から始まり、下から上にレイヤーごとに累積されます。ツリーの深さと高さは同じですが、ツリーの特定のノードの深さと高さが異なります。
方法1:
function TreeDepth(pRoot) {
if (!pRoot) return 0;
var left = 1 + TreeDepth(pRoot.left);
var right = 1 + TreeDepth(pRoot.right);
return Math.max(left, right)
}
方法2:
この方法はルートパスから始まり、質問24の調査で使用されます。パスを記録する配列を見つけることです。リーフノードに到達するたびに、現在のパスの長さが計算され、以前のパスの長さと比較されます。次に、親ノードに戻り、他のパスの長さを計算します。
function TreeDepth(pRoot) {
// write code here
let longest = 0
if (pRoot) {
let path = []
longest = getTreeDepth(pRoot, path, longest)
}
return longest
}
function getTreeDepth(pRoot, path, longest) {
path.push(pRoot.val)
if (!pRoot.left && !pRoot.right && path.length > longest) {
longest = path.length
}
if (pRoot.left) {
longest = getTreeDepth(pRoot.left, path, longest)
}
if (pRoot.right) {
longest = getTreeDepth(pRoot.right, path, longest)
}
path.pop()
return longest
}
質問39:平衡二分木
難易度:♡♡♡
空のツリー、または2つの左と右のサブツリーの高さの差(バランス係数と呼ばれる)が1以下のバイナリソートツリーです。そして、左と右の2つのサブツリーは、バランスのとれた二分木です。
アイデア:バランスのとれた二分木の定義の焦点をしっかりと把握し、左と右のサブツリーはバランスのとれた二分木です
function IsBalanced_Solution(pRoot) {
if (pRoot == null) {
return true
}
if (Math.abs(TreeDepth(pRoot.left) - TreeDepth(pRoot.right)) > 1) {
return false;
} else { //当前节点的左右高度差不大于1
return IsBalanced_Solution(pRoot.left) && IsBalanced_Solution(pRoot.right);//判断左右两个子树都是一棵平衡二叉树吗
}
}
function TreeDepth(pRoot) {
if (!pRoot) return 0;
var left = 1 + TreeDepth(pRoot.left);
var right = 1 + TreeDepth(pRoot.right);
return Math.max(left, right)
}
質問57:二分木の次のノード
難易度:♡♡♡
function GetNext(pNode) {
// write code here
if (!pNode) {
return null
}
//有右子树的
if (pNode.right) {
pNode = pNode.right;
while (pNode.left) { //下个结点就是其右子树最左边的点
pNode = pNode.left
}
return pNode
}
// 没有右子树
while (pNode.next) { //有父节点
let p = pNode.next //p指向当前节点的父节点
if (p.left == pNode) { //直到当前结点是其父节点的左孩子为止
return p
}
pNode = pNode.next
}
return null //尾节点
}
質問58:対称二分木
難易度:♡♡♡♡♡
アイデア:以前に行われた再帰はツリーの再帰であり、このツリーの左と右のサブツリーは再帰的になりました
function isSymmetrical(pRoot) {
// write code here
if (pRoot == null) {
return true
}
return judge(pRoot.left, pRoot.right)
}
function judge(left, right) {
// 以下判断是否都走到底
if (left == null) {
return right == null
}
if (right == null) {
return false
}
// 都未走到底
if (left.val != right.val)
return false
return judge(left.left, right.right) && judge(left.right, right.left)
}
質問59:ジグザグの順序でバイナリツリーを印刷します
難易度:♡♡♡♡
この問題の問題解決方法は、ノードの値がレイヤーの数に応じて左から右に保存されることです。一部の人々(そうです、私です)は、レイヤートラバーサルのコードに取り組んでいます。このステップでは、プッシュが分類され、議論されます。ここでは、左または右を押すと、最終的にハロになりました。
レベルトラバーサルは、1つシフトアウトして、その左と右のノード値にプッシュすることです。ここでは、forループがwhileに追加されています。すばらしいのは、同じレイヤーのノードを処理することです。偶数レイヤーが逆方向出力を必要とする場合でも、レイヤーの順次配列がある限り、配列を逆にするだけで済みます。偶数層のノードを逆方向にトラバースしたいのは誰ですか?
function TreeNode(x) {
this.val = x;
this.left = null;
this.right = null;
}
function Print(pRoot) {
if (!pRoot) return []
let queue = []
let result = []
let flag = true //true奇数
queue.push(pRoot)
while (queue.length) {
let tempArr = [] //用来存放当前层所有节点的值
const len = queue.length //存放当前队列的长度
for (let i = 0; i < len; i++) {
let temp = queue.shift();
tempArr.push(temp.val);
if (temp.left) {
queue.push(temp.left);
}
if (temp.right) {
queue.push(temp.right);
}
}
if (!flag) {
tempArr.reverse();
}
flag = !flag;
result.push(tempArr);
}
return result
}
質問60:バイナリツリーを複数行として印刷します
難易度:♡♡♡
タイトル:バイナリツリーを上から下にレイヤー別に印刷します。同じレイヤーのノードが左から右に出力されます。各レイヤーは1行を出力します。
上記の質問から、特定の順序のすべての値に関するコードを逆の順序で削除するだけです。
質問61:バイナリツリーのシリアル化
難易度:♡♡♡♡
この質問は、質問4のバイナリツリーの再構築に焦点を当てて吐き出したいと考えています。
function Serialize(pRoot) {
if (!pRoot) {
res.push('#');
} else {
res.push(pRoot.val);
Serialize(pRoot.left);
Serialize(pRoot.right);
}
}
function Deserialize(s) {
if (res.length < 1) return null;
let node = null;
let cur = res.shift();
if (typeof cur == 'number') {
node = new TreeNode(cur);
node.left = Deserialize(s);
node.right = Deserialize(s);
}
return node;
}
質問62:二分探索木のk番目に小さいノード
難易度:♡♡♡♡
アイデア:k番目のノードは、中位走査のK番目のノードです。
代码需要注意的地方,一开始我将KthNodeCore(pRoot,<u>k</u>)
放在KthNode
外,明明和书本里C的代码一样却通不过。
后来发现还是因为JavaScript基本数据类型的传参问题,每次的p值改变必须得return回上一轮递归才能在上一轮递归中取得最新p值,但是该函数中我们还需要返回目标节点,因此最好的解决办法就是将k放于递归函数的上一级作用域中。
占个坑:用非递归写一下
占个坑:第K大呢?
function KthNode(pRoot, k) {
// write code here
if (!pRoot || k <= 0)
return null
// 为了能追踪k,应该把KthNodeCore函数定义在这里面,k应该在KthNodeCore函数外面
function KthNodeCore(pRoot) {
let target = null
if (pRoot.left) target = KthNodeCore(pRoot.left)
if (!target) {
if (k == 1) target = pRoot
k--
}
if (!target && pRoot.right) target = KthNodeCore(pRoot.right)
return target
}
return KthNodeCore(pRoot)
}