導入
ツリーは基本的なデータ構造としても非常に実用的であり、ツリーのデータ構造は作業でも非常に一般的に使用されているため、面接官が好むテストのタイプの 1 つであると言えます。そのツリーのほとんどは質問の難易度は比較的中程度であり、そのほとんどは単に面接のために生まれた中難易度の質問です。もちろん、ダイナミック プログラミングやバックトラッキングなど、面接官が好むタイプもあります。実際、ツリー型の質問のほとんどは、2 つのテンプレート メソッド ( 1 つは再帰的手法、もう 1 つは反復手法) をマスターしている限り、解くことができます。これは、ツリー型の質問のほとんどは、詳細検索または広範な検索によって横断されるためです。進行中です。
理論的根拠
ツリーは抽象データ型 (ADT)、またはこの抽象データ型を実装するデータ構造であり、ツリー構造のプロパティを持つデータのコレクションをシミュレートするために使用されます。n(n > 0) 個の有限ノードから構成される階層関係を持つ集合です。根が上を向き、葉が下を向いている、逆さまの木に見えることから
「ツリー」と呼ばれています。木には次のような特徴があります。
- 各ノードには子が限られているか、子が存在しません。
- 親ノードを持たないノードをルートノードと呼びます。
- ルート以外のノードにはすべて、親ノードが 1 つだけあります。
- ルート ノードを除き、各子ノードは複数の互いに素なサブツリーに分割できます。
- ツリーにはループがありません。
木の種類
次に、いくつかの一般的なツリー タイプを紹介します。
- 無順序ツリー: ツリー内のどのノードの子ノード間にも順序関係はありません。この種のツリーは無順序ツリーと呼ばれ、自由ツリーとも呼ばれます。
- 順序付きツリー: ツリー内の任意のノードの子ノード間には順序関係があり、この種のツリーは順序付きツリーと呼ばれます。
- バイナリ ツリー: ノードごとに最大 2 つのサブツリーを持つツリーはバイナリ ツリーと呼ばれ、バイナリ ツリーはチェーンまたは順次に格納できます。
- フルバイナリツリー: リーフノードを除くすべてのノードに 2 つのサブツリーが含まれるツリーは、フルバイナリツリーと呼ばれます。
- 完全二分木: 最後の層を除くすべての層がノードで満たされており、最後の層で正しい連続するノードが欠落している二分木を完全二分木と呼びます。
- ハフマン木 (最適二分木) : 最も短い加重パスを持つ二分木をハフマン木または最適二分木と呼びます。
- 二分探索木: 順序付けられた二分木です。次のプロパティがあります: 左のサブツリーが空でない場合、左のサブツリー上のすべてのノードの値はそのルート ノードの値より小さくなります; 右のサブツリーが空でない場合、右のサブツリー上のすべてのノードの値of はルート ノードの値より大きく、その左右のサブツリーもバイナリ ソート ツリーです。
- 平衡二分探索ツリー: AVL (Adelson-Velsky および Landis) ツリーとも呼ばれ、次の特性があります。空のツリーであるか、左右のサブツリー間の高さの差の絶対値が 1 を超えず、左と右両方のサブツリーはバランスの取れたバイナリ ツリーです。
トラバース式
ツリーには主に 2 つの走査方法があり、これらはグラフ理論における最も基本的な 2 つの走査方法です。
- 深さ優先トラバーサル: 最初に深く進み、リーフ ノードに遭遇したら戻ります。
- 幅優先走査: レイヤーごとに走査します。
トラバーサルを表現するには、事前順序トラバーサル、順序内トラバーサル、事後トラバーサル、および階層トラバーサルの 4 つの方法があります。
たとえば、以下の図では、
事前順序トラバーサルの結果は次のとおりです: ABDECF (ルート-左-右)
順序トラバーサルの結果は次のようになります: DBEAFC (左-ルート-右) (バイナリ ツリーのみが順序トラバーサルを持ちます)
事後走査の結果は次のとおりです: DEBFCA (左-右) 右-ルート)
階層走査の結果は: ABCDEF (幅優先検索と同じ)
、対応する走査メソッドと式は次のとおりです。
- 深さ優先トラバース
- プリオーダートラバーサル(再帰的手法、反復的手法)
- インオーダートラバーサル(再帰的手法、反復的手法)
- ポストオーダートラバーサル(再帰的手法、反復的手法)
- 幅優先トラバース
- 階層トラバーサル (反復法)
ツリートラバーサルを行う場合、深さ優先トラバーサルの再帰的メソッドは当然再帰の実装を指し、スタックが実際には再帰的実装構造であることがわかっているため、反復メソッドはスタックによって実装されます。幅優先トラバーサルの反復方法はキューを使用して実装されます。
コード
ツリーのコード実装
1 つ目はツリーのコード実装です。実際、これは leetcode でのツリー実装です。
public 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;
}
}
バイナリ ツリーのコードはリンク リストのコードと似ていることがわかります。リンク リストと比較すると、バイナリ ツリーのノードにはポインタが 1 つ増え、左右を指すポインタが 2 つあります。子供たち。Leetcode の質問に答えるとき、ノードの定義はデフォルトで定義されていますが、インタビューでは定義が得られないため、自分で練習する必要があります。
深さ優先トラバーサル コードの実装
まず、再帰的手法を使用して中間前と後続の深さ検索コードを記述する方法を説明します. すべての再帰的コードを記述する際には、次の 2 つの点に注意する必要があります。
- 再帰関数のパラメータと戻り値を決定します。再帰プロセス中にどのパラメータを処理する必要があるかを決定し、このパラメータを再帰関数に追加します。また、戻り値の型を決定するために各再帰の戻り値が何であるかを明確にします。再帰関数の。
- 終了条件を決定する: 終了条件がない場合、または終了条件が間違っている場合、プログラムはスタックがオーバーフローするまで続行されます。
事前順序走査再帰
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<Integer>();
preorder(root, result);
return result;
}
public void preorder(TreeNode root, List<Integer> result) {
if (root == null) {
return;
}
result.add(root.val);
preorder(root.left, result);
preorder(root.right, result);
}
}
順序トラバーサル再帰
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
inorder(root, res);
return res;
}
void inorder(TreeNode root, List<Integer> list) {
if (root == null) {
return;
}
inorder(root.left, list);
list.add(root.val); // 注意这一句
inorder(root.right, list);
}
}
事後走査再帰
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
postorder(root, res);
return res;
}
void postorder(TreeNode root, List<Integer> list) {
if (root == null) {
return;
}
postorder(root.left, list);
postorder(root.right, list);
list.add(root.val); // 注意这一句
}
}
反復コードによる再帰の実現
は次のとおりです。各再帰呼び出しはローカル変数、パラメーター値、関数の戻りアドレスをコール スタックにプッシュし、再帰が戻ると、前の再帰のパラメーターが呼び出しスタックからポップされます。これが、再帰が前の位置に戻ることができる理由です。次に、スタックを使用して反復コードを実装します。
プリオーダートラバーサル反復法
/**
* 中-左-右,入栈顺序:中-右-左
*/
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null){
return result;
}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()){
TreeNode node = stack.pop();
result.add(node.val);
if (node.right != null){
stack.push(node.right);
}
if (node.left != null){
stack.push(node.left);
}
}
return result;
}
}
順序トラバーサル反復法
/**
* 顺序: 左-中-右 入栈顺序: 左-右
*/
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null){
return result;
}
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while (cur != null || !stack.isEmpty()){
if (cur != null){
stack.push(cur);
cur = cur.left;
}else{
cur = stack.pop();
result.add(cur.val);
cur = cur.right;
}
}
return result;
}
}
ポストオーダートラバーサル反復法
// 顺序 左-右-中 入栈顺序:中-左-右 出栈顺序:中-右-左, 最后翻转结果
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null){
return result;
}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()){
TreeNode node = stack.pop();
result.add(node.val);
if (node.left != null){
stack.push(node.left);
}
if (node.right != null){
stack.push(node.right);
}
}
Collections.reverse(result);
return result;
}
}
統一された反復法 反復
法によって実現されるインオーダーとインオーダーは、関連するプレオーダーとポストオーダーを除いて、実際にはスタイルがそれほど統一されていません。統一された反復方法はありますか? はい、この方法は記法法とも呼ばれます。
プリオーダートラバーサル統合反復法
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new LinkedList<>();
Stack<TreeNode> st = new Stack<>();
if (root != null) st.push(root);
while (!st.empty()) {
TreeNode node = st.peek();
if (node != null) {
st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
if (node.right!=null) st.push(node.right); // 添加右节点(空节点不入栈)
if (node.left!=null) st.push(node.left); // 添加左节点(空节点不入栈)
st.push(node); // 添加中节点
st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。
} else { // 只有遇到空节点的时候,才将下一个节点放进结果集
st.pop(); // 将空节点弹出
node = st.peek(); // 重新取出栈中元素
st.pop();
result.add(node.val); // 加入到结果集
}
}
return result;
}
}
インオーダートラバーサル統合反復法
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new LinkedList<>();
Stack<TreeNode> st = new Stack<>();
if (root != null) st.push(root);
while (!st.empty()) {
TreeNode node = st.peek();
if (node != null) {
st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
if (node.right!=null) st.push(node.right); // 添加右节点(空节点不入栈)
st.push(node); // 添加中节点
st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。
if (node.left!=null) st.push(node.left); // 添加左节点(空节点不入栈)
} else { // 只有遇到空节点的时候,才将下一个节点放进结果集
st.pop(); // 将空节点弹出
node = st.peek(); // 重新取出栈中元素
st.pop();
result.add(node.val); // 加入到结果集
}
}
return result;
}
}
ポストオーダートラバーサル統合反復法
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result = new LinkedList<>();
Stack<TreeNode> st = new Stack<>();
if (root != null) st.push(root);
while (!st.empty()) {
TreeNode node = st.peek();
if (node != null) {
st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
st.push(node); // 添加中节点
st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。
if (node.right!=null) st.push(node.right); // 添加右节点(空节点不入栈)
if (node.left!=null) st.push(node.left); // 添加左节点(空节点不入栈)
} else { // 只有遇到空节点的时候,才将下一个节点放进结果集
st.pop(); // 将空节点弹出
node = st.peek(); // 重新取出栈中元素
st.pop();
result.add(node.val); // 加入到结果集
}
}
return result;
}
}
幅優先トラバーサル コードの実装
上記の走査コードには多くの内容がありますが、それらはすべて深さ優先検索に関するもので、次のコードは幅優先検索、つまりレイヤー順序の走査に関するものです。先入れ先出しキューはレイヤーごとのトラバーサルのロジックに準拠しますが、先入れ後出しスタックは深さ優先トラバーサルのロジック (再帰) をシミュレートするために使用されます。
レイヤー順序トラバーサル再帰的方法
class Solution {
public List<List<Integer>> resList = new ArrayList<List<Integer>>();
public List<List<Integer>> levelOrder(TreeNode root) {
checkFun(root,0);
return resList;
}
//DFS--递归方式
public void checkFun(TreeNode node, Integer deep) {
if (node == null) return;
deep++;
if (resList.size() < deep) {
//当层级增加时,list的Item也增加,利用list的索引值进行层级界定
List<Integer> item = new ArrayList<Integer>();
resList.add(item);
}
resList.get(deep - 1).add(node.val);
checkFun(node.left, deep);
checkFun(node.right, deep);
}
}
シーケンストラバーサル反復法
class Solution {
public List<List<Integer>> resList = new ArrayList<List<Integer>>();
public List<List<Integer>> levelOrder(TreeNode root) {
checkFun(root);
return resList;
}
//BFS--迭代方式--借助队列
public void checkFun(TreeNode node) {
if (node == null) return;
Queue<TreeNode> que = new LinkedList<TreeNode>();
que.offer(node);
while (!que.isEmpty()) {
List<Integer> itemList = new ArrayList<Integer>();
int len = que.size();
while (len > 0) {
TreeNode tmpNode = que.poll();
itemList.add(tmpNode.val);
if (tmpNode.left != null) que.offer(tmpNode.left);
if (tmpNode.right != null) que.offer(tmpNode.right);
len--;
}
resList.add(itemList);
}
}
}
問題解決の経験
- ツリーは面接官がテストするのに好まれるタイプの 1 つであり、バイナリ ツリーは上位 300 の中で最も人気のあるツリー タイプであるため、これらを習得することに重点を置く必要があります。
- バイナリ ツリーはチェーンで保存することも、順番に保存することもできます。シーケンシャルストレージは配列ストレージを使用しており、その特性により、問題を非常に賢く解決できる場合があります。
- ツリー問題のほとんどは、広範な探索または深い探索の走査プロセスで解決されますが、走査には通常、再帰的手法と反復的手法の 2 つの方法があります。
- ツリーの走査は頻繁にテストされ、一般的な最適化方法はメモリ検索と枝刈りです。
- フロント-ミドル-バック順序のトラバーサルを上手にマスターするには、フロント-ミドル-バック順序でスタックを使用して反復メソッドを実装します。
- 面接中の混乱を避けるために、ツリーのコード定義を自分でマスターする必要があります。
- 統一スタイルの反復法は理解するのが簡単ではありませんが、それをマスターすると、前、中、後ろの順序ですべての反復法のコードを記述するのが簡単になります。
アルゴリズムのトピック
94. 二分木のインオーダートラバース
トピック分析: ポインタとスタックを使用した反復法で、配列を左、中央、右の順に走査します。実際、スタックを使用して反復法を実装することができ、ポストオーダーはプレオーダーを反転することで取得でき、インオーダーはポインタとスタックを判断することで取得できます。
コードは以下のように表示されます。
/**
* Definition for a binary tree node.
* public 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 List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
if (root == null) {
return res;
}
TreeNode cur = root;
while (cur != null || !stack.isEmpty()) {
// 判断有没有到最左子树
if (cur != null) {
stack.push(cur); // 左
cur = cur.left;
} else {
TreeNode temp = stack.pop();
res.add(temp.val); // 中
cur = temp.right; // 右
}
}
return res;
}
}
98. 二分探索木の検証
トピック分析: 再帰。現在の値は、左側のすべてのサブツリーのすべての値より大きく、右側のサブツリーのすべての値より小さくなければならないことに注意してください。
コードは以下のように表示されます。
/**
* Definition for a binary tree node.
* public 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 boolean isValidBST(TreeNode root) {
return helper(root, Long.MIN_VALUE, Long.MAX_VALUE);
}
public boolean helper(TreeNode node, long min, long max) {
if (node == null) {
return true;
}
if (node.val <= min || node.val >= max) {
return false;
}
return helper(node.left, min, node.val) && helper(node.right, node.val, max);
}
}
99. 二分探索木の復元
トピック分析: 順序トラバーサルのプロセスで、2 つの間違ってソートされたノードが記録され、最終的に交換されるため、必要な順序トラバーサルは 1 つだけです。
コードは以下のように表示されます。
/**
* Definition for a binary tree node.
* public 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 {
TreeNode t1, t2, pre;
public void recoverTree(TreeNode root) {
helper(root);
int temp = t1.val;
t1.val = t2.val;
t2.val = temp;
}
public void helper(TreeNode root){
if (root == null) return ;
helper(root.left);
if (pre != null && pre.val > root.val) {
if (t1 == null) t1 = pre;
t2 = root;
}
pre = root;
helper(root.right);
}
}
100. 同じ木
トピック分析: 2 つのツリーを順番に直接走査し、1 つの値が異なる限り戻ります。
コードは以下のように表示されます。
/**
* Definition for a binary tree node.
* public 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 boolean isSameTree(TreeNode p, TreeNode q) {
if(p==null && q==null){
return true;
}
if(p!=null && q!=null && p.val==q.val ){
return isSameTree(p.left,q.left) && isSameTree(p.right,q.right);
}else {
return false;
}
}
}
101. 対称二分木
トピック分析: 再帰的な解決策。ルートが空の場合、それは対称であり、再帰的な出口になります。それ以外の場合は、左側のサブツリーの左側のツリーが右側のサブツリーの右側のツリーと等しいかどうか、および左側のサブツリーの右側のツリーが右側のサブツリーの左側のツリーと等しいかどうかを比較します。
コードは以下のように表示されます。
/**
* Definition for a binary tree node.
* public 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 boolean isSymmetric(TreeNode root) {
// 为空直接返回
if (root == null) {
return true;
}
boolean res = helper(root.left, root.right);
return res;
}
public boolean helper(TreeNode leftTree, TreeNode rightTree) {
// 左右子树同时为空,则不对称
if (leftTree == null && rightTree == null) {
return true;
}
// 一支子树为空,另一支不为空,则不对称
if (leftTree == null || rightTree == null || leftTree.val != rightTree.val) {
return false;
}
// 分别判断:左子树左边树和右子树右边树 and 左子树右边树和右子树左边树 是否相等
boolean res = helper(leftTree.left, rightTree.right) && helper(leftTree.right, rightTree.left);
return res;
}
}
102. 二分木のレベル順走査
トピック分析: 幅を第一に、再帰を使用して値のすべての層をスキャンします。
コードは以下のように表示されます。
/**
* Definition for a binary tree node.
* public 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 List<List<Integer>> levelOrder(TreeNode root) {
return helper(new ArrayList<>(),root,0);
}
List<List<Integer>> helper(List<List<Integer>> res,TreeNode root,int deep){
if(root!=null){
if(res.size()<=deep){
res.add(new ArrayList());
}
List<Integer> list = res.get(deep);
list.add(root.val);
helper(res,root.left,deep+1);
helper(res,root.right,deep+1);
}
return res;
}
}
103. 二分木のジグザグレベル順序走査
トピック分析: 幅を優先し、対応する層でパリティを判断し、先頭と末尾のどちらを挿入するかを決定する必要があります。
コードは以下のように表示されます。
/**
* Definition for a binary tree node.
* public 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 List<List<Integer>> zigzagLevelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
helper(res, root, 0);
return res;
}
private void helper(List<List<Integer>> res, TreeNode root, int deep) {
if (root == null) {
return;
}
if (res.size() == deep) {
res.add(new ArrayList<Integer>());
}
if ((deep & 1) == 1){
res.get(deep).add(0, root.val);
} else {
res.get(deep).add(root.val);
}
helper(res, root.left, deep + 1);
helper(res, root.right, deep + 1);
}
}
104. 二分木の最大深さ
トピック分析: 片手で直接再帰し、左右のサブツリーの高さを返し、それらを比較します。
コードは以下のように表示されます。
/**
* Definition for a binary tree node.
* public 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 maxDepth(TreeNode root) {
if (root == null) {
return 0;
}
int left = maxDepth(root.left);
int right = maxDepth(root.right);
return Math.max(left, right) + 1;
}
}
105. プレオーダーおよびインオーダートラバーサルシーケンスからバイナリツリーを構築する
トピック分析: 事前順序トラバーサルの最初の番号はルートであり、これは順序内で決定され、左のサブツリーは順序の左側にあり、右のサブツリーは右側にあり、その後再帰します。
コードは以下のように表示されます。
/**
* Definition for a binary tree node.
* public 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 Map<Integer, Integer> indexMap;
public TreeNode myBuildTree(int[] preorder, int[] inorder, int preorder_left, int preorder_right, int inorder_left, int inorder_right) {
if(preorder_left > preorder_right)
return null;
//前序遍历的第一个节点为根节点
int preoder_root = preorder_left;
//在中序遍历中定位根节点
int inorder_root = indexMap.get(preorder[preoder_root]);
//先把根节点建立出来
TreeNode root = new TreeNode(preorder[preoder_root]);
//获取左子树中的节点数目
int size_left_subtree = inorder_root - inorder_left;
//递归构造左子树,并连接到根节点
root.left = myBuildTree(preorder, inorder, preoder_root + 1, preoder_root + size_left_subtree, inorder_left, inorder_root - 1);
root.right = myBuildTree(preorder, inorder, preoder_root + size_left_subtree + 1, preorder_right, inorder_root + 1, inorder_right);
return root;
}
public TreeNode buildTree(int[] preorder, int[] inorder) {
int n = preorder.length;
indexMap = new HashMap<>();
//因为前序遍历和中序遍历序列长度一致所以只需一个HashMap
for(int i=0; i<n; i++)
indexMap.put(inorder[i], i);
return myBuildTree(preorder, inorder, 0, n-1, 0, n-1);
}
}
106. インオーダーおよびポストオーダートラバーサルシーケンスからバイナリツリーを構築する
トピック分析: 後置順序の最後の桁までルートを決定し、それを順序内の左右の 2 つのツリーに分割し、後置順序内の 2 つのツリーの後置順序の配置を見つけ、最後に再帰します。
コードは以下のように表示されます。
/**
* Definition for a binary tree node.
* public 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 {
int post_idx;
int[] postorder;
int[] inorder;
Map<Integer, Integer> idx_map = new HashMap<Integer, Integer>();
public TreeNode helper(int in_left, int in_right) {
if (in_left > in_right) {
return null;
}
int root_val = postorder[post_idx];
TreeNode root = new TreeNode(root_val);
int index = idx_map.get(root_val);
post_idx--;
root.right = helper(index + 1, in_right);
root.left = helper(in_left, index - 1);
return root;
}
public TreeNode buildTree(int[] inorder, int[] postorder) {
this.postorder = postorder;
this.inorder = inorder;
post_idx = postorder.length - 1;
int idx = 0;
for (Integer val : inorder) {
idx_map.put(val, idx++);
}
return helper(0, inorder.length - 1);
}
}
107. 二分木のレベル順走査 II
トピック分析: 幅優先検索。キュー ヘッド補間方法を使用して値を保存します。
コードは以下のように表示されます。
/**
* Definition for a binary tree node.
* public 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 List<List<Integer>> levelOrderBottom(TreeNode root) {
LinkedList<List<Integer>> res = new LinkedList<>();
if (root == null)
return res;
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()) {
List<Integer> oneLevel = new ArrayList<>();
// 每次都取出一层的所有数据
int count = queue.size();
for (int i = 0; i < count; i++) {
TreeNode node = queue.poll();
oneLevel.add(node.val);
if (node.left != null)
queue.add(node.left);
if (node.right != null)
queue.add(node.right);
}
// 每次都往队头塞
res.addFirst(oneLevel);
}
return res;
}
}
108. ソートされた配列を二分探索木に変換する
トピック分析: 配列はツリーの順序配置とみなすことができます。中央の番号がルートとして見つかるたびに、左側と右側が左右のサブツリーになります。もちろん、順序ソートのみです。結果は次のようになります。ユニークではありません。
コードは以下のように表示されます。
/**
* Definition for a binary tree node.
* public 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 sortedArrayToBST(int[] nums) {
// 左右等分建立左右子树,中间节点作为子树根节点,递归该过程
return nums == null ? null : buildTree(nums, 0, nums.length - 1);
}
private TreeNode buildTree(int[] nums, int l, int r) {
if (l > r) {
return null;
}
// 求中点不要用 int mid = (l + r)/2,有溢出风险
int m = l + (r - l) / 2;
TreeNode root = new TreeNode(nums[m]);
root.left = buildTree(nums, l, m - 1);
root.right = buildTree(nums, m + 1, r);
return root;
}
}
109. ソート済みリンクリスト変換二分探索木
トピック分析: 高速ポインタと低速ポインタ、再帰。低速ポインタが 1 ステップ実行するたびに、高速ポインタは 2 ステップ実行して中間点 (ルート) を見つけます。
コードは以下のように表示されます。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
/**
* Definition for a binary tree node.
* public 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 sortedListToBST(ListNode head) {
// 快慢指针找到链表的中点,中点作为根结点,两边作为左右子树
if(head == null) return null;
if(head.next == null) return new TreeNode(head.val);
// 快慢指针找中间结点
ListNode fast = head, slow = head, pre = null;
while(fast != null && fast.next != null){
fast = fast.next.next;
pre = slow;
slow = slow.next;
}
// 分割出左链表,用于构造本结点的左子树
pre.next = null;
// 分割出右链表,用于构造本结点的右子树
ListNode rightList = slow.next;
// 用中间结点构造根结点
TreeNode root = new TreeNode(slow.val);
// 构造左子树
root.left = sortedListToBST(head);
// 构造右子树
root.right = sortedListToBST(rightList);
// 返回本结点所在子树
return root;
}
}
110. バランスの取れた二分木
トピック分析:左右のサブツリーを再帰的に検索し、左右のサブツリーの高さの差が絶対値で1を超えるかどうかを判定します。
コードは以下のように表示されます。
/**
* Definition for a binary tree node.
* public 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 boolean isBalanced(TreeNode root) {
return getDepth(root) != -1;
}
// 返回二叉树的深度,如果该二叉树不是平衡二叉树则返回-1
public int getDepth(TreeNode root) {
if (root == null) return 0;
int leftHeight = getDepth(root.left);
int rightHeight = getDepth(root.right);
if (leftHeight == -1 || rightHeight == -1) return -1; // 左右子树出现了非平衡二叉树,直接返回
return Math.abs(leftHeight - rightHeight) > 1 ? -1 : Math.max(leftHeight, rightHeight) + 1;
}
}
111. 二分木の最小深さ
トピック分析: 幅優先検索を実行し、最小値を見つけます。
コードは以下のように表示されます。
/**
* Definition for a binary tree node.
* public 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 boolean hasFound = false;
public int minDepth(TreeNode root) {
if (root == null) {
return 0;
}
Queue<TreeNode> queue = new LinkedList<>();
Queue<Integer> levels = new LinkedList<>();
queue.add(root);
levels.add(1);
while (!queue.isEmpty()) {
TreeNode node = queue.poll();
int level = levels.poll();
if (isLeaf(node)) {
return level;
}
if (node.left != null) {
if (isLeaf(node.left)) {
return level + 1;
}
queue.add(node.left);
levels.add(level + 1);
}
if (node.right != null) {
if (isLeaf(node.right)) {
return level + 1;
}
queue.add(node.right);
levels.add(level + 1);
}
}
return Integer.MAX_VALUE;
}
private boolean isLeaf(TreeNode node) {
return node.left == null && node.right == null;
}
}
112. パスの合計
トピック分析: 再帰中にパスの合計を見つけます。
コードは以下のように表示されます。
/**
* Definition for a binary tree node.
* public 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 boolean hasPathSum(TreeNode root, int targetSum) {
if (root == null) {
return false;
}
if (root.left == null && root.right == null) {
return targetSum - root.val == 0;
}
return hasPathSum(root.left, targetSum - root.val)
|| hasPathSum(root.right, targetSum - root.val);
}
}
113. パスサムⅡ
トピック分析: これは Daoshu に関する質問ですが、問題を解決するために古典的なバックトラッキング テンプレートを使用しています。
コードは以下のように表示されます。
/**
* Definition for a binary tree node.
* public 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 {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> pathSum(TreeNode root, int sum) {
List<Integer> cur = new ArrayList<>();
helper(root, cur, 0, sum);
return res;
}
public void helper(TreeNode node, List<Integer> cur, int sum, int target){
// 1
if(node == null){
return ;
}
if(node.left == null && node.right == null && node.val + sum == target){
cur.add(node.val);
res.add(new ArrayList<>(cur));
cur.remove(cur.size() - 1);
return ;
}
// 2
// 2.1
cur.add(node.val);
// 2.2
helper(node.left, cur, sum + node.val, target);
helper(node.right, cur, sum + node.val, target);
// 2.3
cur.remove(cur.size() - 1);
}
}
114. バイナリ ツリーをリンク リストに展開する
トピック分析: 再帰的に、左端のサブツリーから開始して、左のツリーを上向きのルートの右のツリーに配置し、左のツリーを空のままにし、元の右のツリーを現在の右のツリーの右端のサブツリーに配置します。
コードは以下のように表示されます。
/**
* Definition for a binary tree node.
* public 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 void flatten(TreeNode root) {
if(root == null) return;
flatten(root.left);
flatten(root.right);
TreeNode tmp = root.right;
root.right = root.left;
root.left = null;
while(root.right != null) root = root.right;
root.right = tmp;
}
}
116. 各ノードの次の右のノード ポインターを設定します。
トピック分析: 再帰、ルートの接続が完了し、左右のサブツリーを借用できるため、一定の追加スペース (root.left.next = root.right; root.right) だけでトピックを完成させることができます。次 = root.next .left;。
コードは以下のように表示されます。
/*
// Definition for a Node.
class Node {
public int val;
public Node left;
public Node right;
public Node next;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, Node _left, Node _right, Node _next) {
val = _val;
left = _left;
right = _right;
next = _next;
}
};
*/
/**
* 递归
*/
class Solution {
public Node connect(Node root) {
if (root == null) {
return null;
}
if (root.left != null) {
// 连接左右子树
root.left.next = root.right;
if (root.next != null) {
// 右子树的next,可以通过root的连接找到
root.right.next = root.next.left;
}
}
connect(root.left);
connect(root.right);
return root;
}
}
117. 各ノードの右隣のノードポインタを埋める II
トピック分析: 横断するには、各レイヤーに存在するすべてのポイントを仮想ヘッド ノードで接続します。
コードは以下のように表示されます。
/*
// Definition for a Node.
class Node {
public int val;
public Node left;
public Node right;
public Node next;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, Node _left, Node _right, Node _next) {
val = _val;
left = _left;
right = _right;
next = _next;
}
};
*/
/**
* 遍历
*/
class Solution {
public Node connect(Node root) {
if (root == null) {
return root;
}
Node cur = root;
while (cur != null) {
// 用一个头节点标注下一层的链表开始
Node head = new Node(0);
// 新建虚拟头节点
Node pre = head;
// 用当前层去更改下一层
while (cur != null) {
// 只要左、右子节点存在,则用头节点串起来
if (cur.left != null) {
head.next = cur.left;
head = head.next;
}
if (cur.right != null) {
head.next = cur.right;
head = head.next;
}
cur = cur.next;
}
// 移动到下一层,这里 pre.next(head.next) 为下一层第一个节点
cur = pre.next;
}
return root;
}
}
129. ルートノードからリーフノードまでの数値の合計を求めます
トピック分析: 直接再帰、深さ優先検索で十分です。
コードは以下のように表示されます。
/**
* Definition for a binary tree node.
* public 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 sumNumbers(TreeNode root) {
return helper(root, 0);
}
public int helper(TreeNode root, int temp) {
int res = 0;
if (root == null) {
return temp;
}
temp = temp * 10 + root.val;
// 如果左子树不为空,递归出左子树的值
if (root.left != null) {
res += helper(root.left, temp);
}
// 如果右子树不为空,递归出右子树的值
if (root.right != null) {
res += helper(root.right, temp);
}
// 如果左、右子树为空,返回本叶子节点值
if (root.left == null && root.right == null) {
res = temp;
}
return res;
}
}
140. 単語分割 II
トピック分析: 記憶された検索の最適化にハッシュ テーブルを使用し、検索の繰り返しを回避します。
コードは以下のように表示されます。
/**
* 记忆化搜索
*/
class Solution {
public List<String> wordBreak(String s, List<String> wordDict) {
Map<Integer, List<List<String>>> map = new HashMap<Integer, List<List<String>>>();
List<List<String>> wordBreaks = backtrack(s, s.length(), new HashSet<String>(wordDict), 0, map);
List<String> breakList = new LinkedList<String>();
for (List<String> wordBreak : wordBreaks) {
breakList.add(String.join(" ", wordBreak));
}
return breakList;
}
public List<List<String>> backtrack(String s, int length, Set<String> wordSet, int index, Map<Integer, List<List<String>>> map) {
if (!map.containsKey(index)) {
List<List<String>> wordBreaks = new LinkedList<List<String>>();
if (index == length) {
wordBreaks.add(new LinkedList<String>());
}
for (int i = index + 1; i <= length; i++) {
String word = s.substring(index, i);
if (wordSet.contains(word)) {
List<List<String>> nextWordBreaks = backtrack(s, length, wordSet, i, map);
for (List<String> nextWordBreak : nextWordBreaks) {
LinkedList<String> wordBreak = new LinkedList<String>(nextWordBreak);
wordBreak.offerFirst(word);
wordBreaks.add(wordBreak);
}
}
}
map.put(index, wordBreaks);
}
return map.get(index);
}
}
144. バイナリツリーの事前注文トラバーサル
トピック分析: 再帰の代わりにスタックを使用する反復法 (再帰では基本的にローカル変数値を保存するためにスタックも使用する必要があります)、値を中央、右、左、の順序でスタックに保存します。ループが作成されるたびに、順番にポップアップが表示されます。なぜ最初にプッシュする必要があるのですか? 右のサブツリーの値を最初に保存できるため、右のサブツリーの値が左のサブツリーの値にプッシュされます。右サブツリーの値を最初に格納し、スタックを使用して反復メソッドを実装することができ、ポストオーダーをプレオーダーから逆にすることができます。順序はポインタの判定とスタックから取得できます。
コードは以下のように表示されます。
/**
* Definition for a binary tree node.
* public 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 List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
if (root == null) {
return res;
}
// 首先把根节点压入栈
stack.push(root);
while (!stack.isEmpty()) {
TreeNode temp = stack.pop();
// 前序遍历,先把当前节点加入集合
res.add(temp.val); // 中
// 如果右节点不为空则把其加入栈
if(temp.right != null) stack.push(temp.right); // 右
// 如果左节点不为空则把其加入栈
if(temp.left != null) stack.push(temp.left); // 左
}
return res;
}
}
145. 二分木の事後走査
トピック分析: 再帰の代わりにスタックを使用する反復法 (再帰は本質的に、ローカル変数の値を保存するためにもスタックを使用する必要があります)、中央、左、右の順序で値をスタックに保存します。その結果、中央、右、左の「事前順序」トラバースが得られ、その後セットが逆になります。つまり、左右の事後トラバース セットになります。反復法を実現するためにスタックを利用することができ、プレオーダーを逆にするとポストオーダーが得られ、ポインタとスタックを判定することでインオーダーが得られます。
コードは以下のように表示されます。
/**
* Definition for a binary tree node.
* public 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 List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
if (root == null) {
return res;
}
// 首先把根节点压入栈
stack.push(root);
while (!stack.isEmpty()) {
TreeNode temp = stack.pop();
// 后序遍历,先压根节点(之后会反转)
res.add(temp.val);
// 如果左节点不为空则把其加入栈
if (temp.left != null) stack.push(temp.left); // 左 先压入左子树节点,这样后出栈,进而组成 中右左 组合
// 如果左节点不为空则把其加入栈
if (temp.right != null) stack.push(temp.right); // 右
}
Collections.reverse(res);
return res;
}
}
199. 二分木の右面図
トピック分析: レイヤーごとに移動し、毎回、必要な要素である右端の要素を追加します。
コードは以下のように表示されます。
/**
* Definition for a binary tree node.
* public 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 List<Integer> rightSideView(TreeNode root) {
List<Integer> res = new ArrayList<>();
if(root == null) return res;
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while(!queue.isEmpty()){
int count = queue.size();
while(count > 0){
count--;
TreeNode cur = queue.poll();
if(count == 0){
res.add(cur.val);
}
if(cur.left != null){
queue.add(cur.left);
}
if(cur.right != null){
queue.add(cur.right);
}
}
}
return res;
}
}
212. 単語検索Ⅱ
トピック分析: これも後戻りテンプレートの質問です。テンプレートについては後ほど詳しく説明しますので、ここで理解してください。
コードは以下のように表示されます。
/**
* 回溯 + 字典树
*/
class Solution {
int[][] dirs = {
{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
public List<String> findWords(char[][] board, String[] words) {
Trie trie = new Trie();
for (String word : words) {
trie.insert(word);
}
Set<String> ans = new HashSet<String>();
for (int i = 0; i < board.length; ++i) {
for (int j = 0; j < board[0].length; ++j) {
dfs(board, trie, i, j, ans);
}
}
return new ArrayList<String>(ans);
}
public void dfs(char[][] board, Trie now, int i1, int j1, Set<String> ans) {
if (!now.children.containsKey(board[i1][j1])) {
return;
}
char ch = board[i1][j1];
now = now.children.get(ch);
if (!"".equals(now.word)) {
ans.add(now.word);
}
board[i1][j1] = '#';
for (int[] dir : dirs) {
int i2 = i1 + dir[0], j2 = j1 + dir[1];
if (i2 >= 0 && i2 < board.length && j2 >= 0 && j2 < board[0].length) {
dfs(board, now, i2, j2, ans);
}
}
board[i1][j1] = ch;
}
}
class Trie {
String word;
Map<Character, Trie> children;
boolean isWord;
public Trie() {
this.word = "";
this.children = new HashMap<Character, Trie>();
}
public void insert(String word) {
Trie cur = this;
for (int i = 0; i < word.length(); ++i) {
char c = word.charAt(i);
if (!cur.children.containsKey(c)) {
cur.children.put(c, new Trie());
}
cur = cur.children.get(c);
}
cur.word = word;
}
}
222. 完全な二分木のノードの数
トピック分析: 完全なバイナリ ツリーの技術的解決策の場合、完全なバイナリ ツリー内のノードの数は次のとおりです: 2^深さ - 1、完全なバイナリ ツリーはそれぞれ左と右のサブツリーに分割され、最終的に小さなサブツリーに分割できます。完全なバイナリ ツリー、次に小さなバイナリ ツリーの順に計算し、式を使用して計算します。
コードは以下のように表示されます。
/**
* Definition for a binary tree node.
* public 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 countNodes(TreeNode root) {
if (root == null) {
return 0;
}
int leftDepth = getDepth(root.left);
int rightDepth = getDepth(root.right);
if (leftDepth == rightDepth) {
// 左子树是满二叉树
// 2^leftDepth其实是 (2^leftDepth - 1) + 1 ,左子树 + 根结点
return (1 << leftDepth) + countNodes(root.right);
} else {
// 右子树是满二叉树
return (1 << rightDepth) + countNodes(root.left);
}
}
private int getDepth(TreeNode root) {
int depth = 0;
while (root != null) {
root = root.left;
depth++;
}
return depth;
}
}
226. 二分木を反転する
トピック分析: 再帰、再帰は下位層から上位層に交換されます。
コードは以下のように表示されます。
/**
* Definition for a binary tree node.
* public 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 invertTree(TreeNode root) {
return helper(root);
}
public TreeNode helper(TreeNode root) {
if (root == null) {
return root;
}
TreeNode temp = helper(root.left);
root.left = helper(root.right);
root.right = temp;
return root;
}
}
230. 二分探索木の K 番目に小さい要素
トピック分析: サブツリー内のノード数を格納するには、バイナリ ツリー データ構造を自分で構築する必要があります。
コードは以下のように表示されます。
/**
* Definition for a binary tree node.
* public 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 kthSmallest(TreeNode root, int k) {
MyBst bst = new MyBst(root);
return bst.kthSmallest(k);
}
}
class MyBst {
TreeNode root;
Map<TreeNode, Integer> nodeNum;
public MyBst(TreeNode root) {
this.root = root;
this.nodeNum = new HashMap<TreeNode, Integer>();
countNodeNum(root);
}
// 返回二叉搜索树中第k小的元素
public int kthSmallest(int k) {
TreeNode node = root;
while (node != null) {
int left = getNodeNum(node.left);
if (left < k - 1) {
node = node.right;
k -= left + 1;
} else if (left == k - 1) {
break;
} else {
node = node.left;
}
}
return node.val;
}
// 统计以node为根结点的子树的结点数
private int countNodeNum(TreeNode node) {
if (node == null) {
return 0;
}
nodeNum.put(node, 1 + countNodeNum(node.left) + countNodeNum(node.right));
return nodeNum.get(node);
}
// 获取以node为根结点的子树的结点数
private int getNodeNum(TreeNode node) {
return nodeNum.getOrDefault(node, 0);
}
}
235. 二分探索木の最も近い共通祖先
トピック分析: 二分探索木の特性によれば、p.val < root.val < q.val の場合、root は p と q の最も近い共通の祖先です。
コードは以下のように表示されます。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
/**
* 树
*/
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root.val == p.val) {
return p;
}
if (root.val == q.val) {
return q;
}
// 如果p.val,q.val都小于root.val,则说明两值都在root的左子树中
if (p.val < root.val && q.val < root.val) {
return lowestCommonAncestor(root.left, p, q);
}
// 如果p.val,q.val都大于root.val,则说明两值都在root的右子树中
if (p.val > root.val && q.val > root.val) {
return lowestCommonAncestor(root.right, p, q);
}
return root;
}
}
236. 二分木の最も近い共通祖先
トピック分析: 再帰、ルートが空、p、q はルートの場合は直接ルートを返し、左と右に再帰、空でない場合は、一方が左側にあり、もう一方が右側にあり、ルートに戻ります。このとき、空でない人は誰でも返します。
コードは以下のように表示されます。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
/**
* 递归
*/
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
// 找到叶子节点或找到其中一个节点,即可返回
if (root == null || p == root || q == root) return root;
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
// 节点在左右子树,返回当前根节点
if (left != null && right != null) return root;
// 返回最近的根节点
return left == null ? right : left;
}
}
257. 二分木のすべてのパス
トピック分析: 再帰的に直接スタッドし、パスを出力します。
コードは以下のように表示されます。
/**
* Definition for a binary tree node.
* public 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 List<String> binaryTreePaths(TreeNode root) {
List<String> res = new ArrayList<>();
helper(res, root, "");
return res;
}
public void helper(List<String> res, TreeNode root, String p){
if(root != null){
StringBuilder str = new StringBuilder(p);
str.append(root.val);
if(root.left == null && root.right == null){
res.add(str.toString());
return;
}
else{
str.append("->");
String sb = str.toString();
helper(res,root.left,sb);
helper(res,root.right,sb);
}
}
}
}
ホームページに戻る
Leetcode 500 以上の質問を磨くことについての感想