各バイナリ ツリーの質問タイプを説明する前に、バイナリ ツリー内のノードを表すクラスを作成します。コードは次のとおりです。
class Node {
int value;
Node left;
Node right;
public Node() {
}
public Node(int value) {
this.value = value;
}
}
1. 2 つの二分木ノードノード 1 とノード 2 が与えられた場合、それらの最も低い共通の祖先ノードを見つけます。
アイデア:
方法1: ①ハッシュテーブルHashMapを作成し、バイナリツリー内の全ノードの親ノードを走査する
②node1までのすべての祖先ノードを記録するハッシュテーブルHashSetを作成する
③ ノード 2 はすべての祖先ノードを順番に走査し、HashSet によって記録されたノード 1 の祖先ノードがノード 2 の走査プロセスで初めて現れます。このノードは 2 つのノードの中で最も低い共通祖先ノードになります。
方法 2: (このメソッドのコード量は非常に少ないですが、理解するのが難しいため、理解を助けるために自分でコードを理解する必要があります) 最下位共通祖先は次の 2 つのケースのみです。1) ノード 1 はノード 1 とノード 2 の最低共通祖先、またはノード 2 はノード 1 とノード 2 です。 2) の最低共通祖先は、ノード 1 とノード 2 が互いの最低共通祖先ではありません。ノード 1 とノード 2 は、上向きに収束する場合にのみ見つかります。
方法 1 のコード実装は次のとおりです。
//找到两个节点node1、node2的最低公共祖先
//方法一
public Node findLowestAncestor1(Node head,Node node1,Node node2){
HashMap<Node,Node> fatherMap=new HashMap<>();
fatherMap.put(head,head);
process3(head,fatherMap);
HashSet<Node> node1AncestorSet=new HashSet<>();
Node cur=node1;
while (cur!=fatherMap.get(cur)){//从下往上回溯,只有头节点head的父节点是自己
node1AncestorSet.add(cur);
cur=fatherMap.get(cur);
}
node1AncestorSet.add(head);
Node temp=node2;
while (temp!=fatherMap.get(temp)){
if (node1AncestorSet.contains(temp)){
return temp;
}
temp=fatherMap.get(temp);
}
return head;//只有当第二个while中没有找到公共祖先,才可能运行这行代码,head一定是node1、node2的公共祖先
}
public void process3(Node head,HashMap<Node,Node> fatherMap){//process3这个方法作用是记录所有节点的父节点是哪个(head除外)
if (head==null){
return;
}
fatherMap.put(head.left,head);
fatherMap.put(head.right,head);
process3(head.left,fatherMap);
process3(head.right,fatherMap);
}
方法 2 のコード実装は次のとおりです。
//方法二
public Node findLowestAncestor2(Node head,Node node1,Node node2){
Node cur=head;
if (cur==null||cur==node1||cur==node2){
return cur;
}
Node left=findLowestAncestor2(cur.left,node1,node2);
Node right=findLowestAncestor2(cur.right, node1, node2);
if (left!=null&&right!=null){
return cur;
}
return left!=null?left:right;
}
2. 二分木の最大幅を取得します。
アイデア: 幅優先トラバーサル + ハッシュ テーブルを使用して現在のノードが配置されているレイヤーを記録し、各レイヤーのノード数を数えて、最大のものを取得します。
アルゴリズムのコードは次のとおりです。
//获取二叉树的最大宽度
public int getMaxWidth(Node head) {
if (head == null) {
return 0;
}
Queue<Node> queue = new LinkedList<>();//这是多态的用法,这样写也是对的:LinkedList<Node> queue=new LinkedList<>();
HashMap<Node, Integer> hashMap = new HashMap<>();
queue.add(head);
hashMap.put(head, 1);
int curLevel = 1;//当前在哪一层
int curNodeLevel;//表示当前节点属于哪一层
int curLevelNodes = 0;//当前层节点的个数
int max = Integer.MIN_VALUE;//统计节点数最多那层节点的个数,max=0,-1等等都行
Node cur;//表示当前节点
while (!queue.isEmpty()) {
cur = queue.poll();
curNodeLevel = hashMap.get(cur);
if (curNodeLevel == curLevel) {
curLevelNodes++;
} else {
max = Math.max(max, curLevelNodes);
curLevel++;
curLevelNodes = 1;
}
if (cur.left != null) {
hashMap.put(cur.left, curNodeLevel + 1);
queue.add(cur.left);
}
if (cur.right != null) {
hashMap.put(cur.right, curNodeLevel + 1);
queue.add(cur.right);
}
}
max = Math.max(max, curLevelNodes);//如果不加这一行,那么最后一层的节点个数就没有作比较
return max;
}
3. 二分木が検索二分木であるかどうかを判断します。
二分木探索の特徴は、各部分木において、左ノード(部分木の先頭ノード)がそれより小さく、右ノードがそれより大きいことである。この場合、二分探索木に対して順序どおりの走査を実行すると、小さいものから大きいものへのシーケンスが得られることを見つけるのは難しくありません。
アイデア:順序トラバーサルを使用します。各サブツリーの左右のノードの値が昇順であることを確認するだけです。
コードは次のように実装されます。
//判断二叉树是否是搜索二叉树
//方法一:中序递归遍历判断(还有一种递归方法就是创建一个集合,按中序遍历把每次遍历的节点依次加入集合中,如果集合中的树是按升序排列的,就是搜索二叉树)
int preValue = Integer.MIN_VALUE;//这个变量用来表示(左头右顺序中)前一个节点的值,之所以用Integer.MIN_VALUE进行初始化,
// 是因为中序遍历第一次打印的是整棵树最底部最左侧的那个节点,此节点没有左树和右数,所以不管这个节点的值是多少都一定符合搜索二叉树的特点
//(这和任意值(打印的第一个节点的值)一定比系统最小值(该节点左树节点的值,其实该节点没有左树)大是一个道理)
//也可以这样理解:中序遍历过程中,左节点可以看成是头节点的前一个值,头节点可以看成是右节点的前一个值,当前节点和前一个节点的值preValue比较后,
// 便将当前节点的值赋值给preValue,依次传递下去
public boolean isSBTRecur(Node head) {
if (head == null) {
return true;//这个初始化很重要,这里的head并不是说就只是整棵树的头节点,其实后面递归之后每个节点都有机会是head
}
//判断左树是否是搜索二叉树
boolean isLeftSbt = isSBTRecur(head.left);
if (!isLeftSbt) {
return false;
}
//整个树是不是搜索二叉树(判断条件),这个判断条件放中间就是因为必须是中序遍历,判断条件才成立,先序和后序判断条件都无意义
if (head.value <= preValue) {//意思是如果当前节点的值小于前一节点(此处的前一节点不是固定的含义,可以是左节点,也可以是头节点)的值
return false;
} else {
preValue = head.value;
}
//判断右树是否是搜索二叉树
boolean isRightSbt = isSBTRecur(head.right);
return isRightSbt;
}
//方法二、中序非递归遍历判断,这方法比递归好理解,简单,但代码复杂,自己去写
4. バイナリ ツリーが完全なバイナリ ツリーであるかどうかを判断します。
アイデア: 幅優先トラバーサルを使用します。トラバースされる各ノードは、次の 2 つの条件を同時に満たす必要があります。 ① 右があり、左がない、false ② 不完全な左と右の子を持つ最初のノードが見つかった場合、後続のノードはすべてリーフ ノードです(左も右もありません)
コードは次のように実装されます。
//判断二叉树是否是完全二叉树
public boolean isCBT(Node head) {
if (head == null) {
return true;
}
LinkedList<Node> queue = new LinkedList<>();
boolean leaf = false;//表示是否遇到左右孩子不双全的节点
Node left;
Node right;//分别表示左孩子和右孩子
Node cur;//表示当前节点
queue.add(head);
while (!queue.isEmpty()) {
cur = queue.poll();
left = cur.left;
right = cur.right;
if ((leaf && (left != null || right != null)) || (left == null && right != null)) {
//leaf表示是否遇到左右孩子不双全的节点,left!=null||right!=null表示当前节点不是叶节点,left==null&&right!=null表示有右无左
return false;
}
if (left != null) {
queue.add(left);
}
if (right != null) {
queue.add(right);
}
if (left == null || right == null) {
leaf = true;
}
}
return true;
}
5. 検索二分木が平衡二分木であるかどうかを判断します。
コードは次のように実装されます。
//判断搜索二叉树是否是平衡二叉树(此处用的方法是二叉树问题的递归套路,此处的递归和之前的递归还是有所不同的,是否二叉树的相关问题都可以用这个套路解决)
//即树型DP(DP是动态规划的意思)的问题都可以用这个套路完成
public boolean isBanlanced(Node head) {
return process1(head).isBalanced;
}
public class ReturnType1 {
boolean isBalanced;
int height;
public ReturnType1(boolean isBalanced, int height) {
this.isBalanced = isBalanced;
this.height = height;
}
}
//注意:左树、右树、整棵树进行的操作内容都一样才能构成递归(比如此处都是返回ReturnType类型的数据)
public ReturnType1 process1(Node x) {
if (x == null) {
return new ReturnType1(true, 0);
}//这个if语句很重要,相当于就是初始条件
ReturnType1 leftData = process1(x.left);
ReturnType1 rightData = process1(x.right);
int height = Math.max(leftData.height, rightData.height) + 1;
boolean isBanlanced = leftData.isBalanced && rightData.isBalanced && Math.abs(leftData.height - rightData.height) < 2;
return new ReturnType1(isBanlanced, height);
}
6. バイナリ ツリーがいっぱいかどうかを判断します。
アイデア: ツリー型 DP は再帰ルーチンによって解決されます。再帰情報はレベルと n 情報を伝える必要があります。つまり、戻り値の型はこれら 2 つのメンバー変数を含むクラスです。左のツリーは返すことができ、右のツリーは返すことができます、それ自体が戻ることができます。同時に再帰を構成することに満足しています
コードは次のように実装されます。
//判断二叉树是否为满二叉树,这为树型DP问题,用套路解决
public boolean isFBT(Node head) {
return process2(head).nodes == 1 << (process2(head).height) - 1;//1左移一位就是2^1,左移两位就是2^2,……;某个数右移一位就是这个数除以2^1,……
}
public class ReturnType2 {
int height;
int nodes;
public ReturnType2(int height, int nodes) {
this.height = height;
this.nodes = nodes;
}
}
public ReturnType2 process2(Node x) {
if (x == null) {
return new ReturnType2(0, 0);
}
ReturnType2 leftData = process2(x.left);
ReturnType2 rightData = process2(x.right);
int height = Math.max(leftData.height, rightData.height) + 1;
int nodes = leftData.nodes + rightData.nodes + 1;
return new ReturnType2(height, nodes);
}