1. バイナリツリーの最大深さ
バイナリ ツリーの最大深さ
バイナリ ツリー ルートを指定すると、その最大深さを返します。
バイナリ ツリーの最大の深さは、ルート ノードから最も遠いリーフ ノードまでの最長パス上のノードの数です。
1.1 再帰
上記の手順からわかるように、深さは左右のサブツリーに依存するため、左のサブツリーが存在する限り高さは +1 され、右のサブツリーが存在する場合は高さを計算するだけで済みます。左右のサブツリーを作成し、高さを比較して 1 を追加します。
public int maxDepth(TreeNode root) {
if(root==null) return 0;
int leftHight = maxDepth(root.left);
int rightHight = maxDepth(root.right);
return Math.max(leftHight,rightHight)+1;
}
1.2 レイヤー順序のトラバーサル
これは比較的単純で、トラバースするたびに結果に 1 を加算するだけでよく、通常は階層的なトラバース操作です。
public int maxDepth(TreeNode root) {
if(root==null) return 0;
LinkedList<TreeNode> queue = new LinkedList<>();
queue.offer(root);
// 计算深度
int res =0 ;
while(!queue.isEmpty()){
// 每一层的元素数
int size = queue.size();
while(size>0){
TreeNode node = queue.poll();
if(node.left!=null){
queue.offer(node.left);
}
if(node.right!=null){
queue.offer(node.right);
}
size--;
}
res++;
}
return res;
}
2. バランスの取れた二分木
バランスのとれた二分木
与えられた二分木が、高さのバランスの取れた二分木であるかどうかを判断します。この質問では、高さバランスのとれた二分木は、二分木の各ノードの左右のサブツリー間の高さの差の絶対値が 1 を超えないもの
として定義されます。
2.1 トップダウン再帰
まず、二分木を左右のツリーに分割し、左右のツリーの高さを別々に計算する必要がありますが、子ノードが存在する限り高さは +1 されます。
public boolean isBalanced(TreeNode root) {
if(root == null) return true;
// 左树的高度
int leftHight =getTreeHight(root.left);
// 右树的高度
int rightHight =getTreeHight(root.right);
// 判断高度差,递归
return Math.abs(leftHight-rightHight)<=1 && isBalanced(root.left) && isBalanced(root.right);
}
// 获取当前节点的高度
public int getTreeHight(TreeNode node){
if(node == null) return 0;
int leftHight = getTreeHight(node.left);
int rightHight=getTreeHight(node.right);
return Math.max(leftHight,rightHight)+1;
}
当初は層順走査を使いたかったのですが、左右のサブツリーの状況を考慮していませんでした。ただし、上記の再帰を上から下に使用してすべての高さを計算します。時間計算量: O(n^2 )という無駄な性能です。
2.2 ボトムアップ再帰
この方法の利点は、ポストオーダートラバーサル、左右のルートの方法に似ており、最も左の要素が最初であり、同じツリーに属する右側のサブツリーが並んでいる場合、または右側のサブツリーがない場合に発生します。現時点ではバランスが取れていると見なされ、高さが合わなくなるまで再帰を続けて終了します。
public boolean isBalanced(TreeNode root) {
return getTreeHight(root) >=0;
}
// 获取当前节点的高度
public int getTreeHight(TreeNode root){
if(root == null) return 0;
int leftHight = getTreeHight(root.left);
int rightHight = getTreeHight(root.right);
// 不满足条件leftHight=-1标识没有左子树,rightHight=-1类似
if(leftHight == -1 || rightHight == -1 || Math.abs(leftHight-rightHight)>1){
return -1;
}else{
return Math.max(leftHight,rightHight)+1;
}
}
3. 二分木の最小深さ
二分木の最小深さ
与えられた二分木の最小深さを見つけます。
最小深さは、ルート ノードから最も近いリーフ ノードまでの最短パス上のノードの数です。
注: リーフ ノードは、子ノードを持たないノードを指します。
入力: root = [3,9,20,null,null,15,7]
出力:2
3.1 再帰
まず、左側と右側の子はすべて空です。これは、それらがリーフ ノードであることを意味します。左右のノードのいずれかが空の場合、必要な深さが別の
サブツリーの深さ + 1 であることを意味する1
が返されます。左右のノードが存在する場合は、より小さい深さが選択されます。
public int minDepth(TreeNode root) {
if(root == null) return 0;
// 只有根节点,此时就是叶子节点
if(root.left==null && root.right == null) return 1;
// 左子树深度
int leftLength = minDepth(root.left);
// 右子树深度
int rightLength = minDepth(root.right);
// 树的深度:非空子树的深度加上根节点本身,只要有一个子树为空,那么其对应的深度就是0
if(root.left == null || root.right == null) return leftLength+rightLength+1;
// 左右子树都不为空,深度就是左右子树较小的加上本身
return Math.min(leftLength,rightLength)+1;
}
3.2 レイヤー順序のトラバーサル
階層順走査の方が分かりやすく、葉ノードかどうかを判断するだけでよく、葉ノードであれば現在の深さを直接返し、葉ノードでなければ追加を続けます。
public int minDepth(TreeNode root) {
if(root ==null) return 0;
LinkedList<TreeNode> queue = new LinkedList<>();
queue.add(root);
int minLength =0;
while(!queue.isEmpty()){
int size = queue.size();
minLength++;
for(int i=0;i<size;i++){
TreeNode node =queue.remove();
// 遇到叶子节点就返回
if(node.left==null && node.right==null){
return minLength;
}
if(node.left!=null){
queue.add(node.left);
}
if(node.right!=null){
queue.add(node.right);
}
}
}
return 0;
}
再帰と比較すると、レイヤー順序のトラバーサルはイベントであり、対応するツリー ノードのサブツリーが空であること、つまりリーフ ノードが見つかり、深さが返されることだけを満たせばよいため、空間が高速になります。最初のリーフ ノードが最後に見つかります。それが空の場合は、すべてのバイナリ ツリーを走査する必要はありません。
4. N分木の最大深さ
N 分ツリーの最大深度
N 分ツリーが与えられた場合、その最大深さを求めます。
最大深度は、ルート ノードから最も遠いリーフ ノードまでの最長パス上のノードの総数を指します。
N 分ツリー入力は、子ノードの各セットが null 値で区切られて、レベル順序トラバーサルでシリアル化されます (例を参照)。
4.1 レイヤー順序のトラバーサル
この質問はバイナリ ツリーに似ていますが、サブ要素をたどってからサブ要素を追加する必要がある点が異なります。
public int maxDepth(Node root) {
if(root == null) return 0;
LinkedList<Node> queue = new LinkedList<>();
queue.add(root);
int minLength = 0;
while(!queue.isEmpty()){
int size = queue.size();
minLength++;
for(int i=0;i<size;i++){
Node node = queue.poll();
List<Node> children = node.children;
for(Node child:children){
queue.add(child);
}
}
}
return minLength;
}
4.2 再帰
public int maxDepth(Node root) {
if(root == null)
{
return 0;}
else if(root.children.isEmpty())
{
return 1;}
else{
List<Integer> hight = new ArrayList<>();
List<Node> children = root.children;
for(Node child:children){
hight.add(maxDepth(child));
}
return Collections.max(hight)+1;
}
}
要約する
バイナリ ツリーの深さと高さについては、再帰を使用すると非常に高速ですが、欠点は境界を考慮する必要があることです。再帰的な解決策が思いつかない場合もあります。再帰はまだ馴染みがなく、層の順序のトラバースは簡単です理解して考える必要がありますが、デメリット その理由は、コードが比較的長いためです。