二叉樹遞迴練習

LingLee_荊棘鳥發表於2017-07-17

(1)
題目:

請把紙條豎著放在桌面上,然後從紙條的下邊向上⽅對摺,壓出摺痕後再展開。此時有1條摺痕,突起的⽅向指向紙條

的背面,這條摺痕叫做“下”摺痕 ;突起的面向指向紙條正⾯的摺痕叫做“上”摺痕。如果每次都從下邊向上面對

折,對摺N次。請從上到下計算出所有摺痕的面向。

給定折的次數n,請返回從上到下的摺痕的陣列,若為下摺痕則對應元素為"down",若為上摺痕則為"up".

思路:找張紙試幾次就會發現,這其實是一棵滿二叉樹,樹的根是“下”,左孩子是“上”,右孩子是“下”。遍歷

順序是“右,中,左”


public class FoldPaper {
    public String[] foldPaper(int n) {
        // write code here
        List<String> list=new ArrayList();
        fold(1,n,true,list);
        String[] res=new String[list.size()];
        for(int i=0;i<list.size();i++){
            res[i]=list.get(i);
        }
        return res;
    }
    
    public void fold(int i,int n,boolean down,List<String> list){//i--當前層數    遍歷順序:右,中,左,(左上右下的樹)
        if(i>n) return ;
        fold(i+1,n,true,list);
        list.add(down?"down":"up");//加入down
        fold(i+1,n,false,list);
    }
}


(2)


一棵二叉樹原本是搜尋二叉樹,但是其中有兩個節點調換了位置,使得這棵二叉樹不再是搜尋二叉樹,請找到這兩個

錯誤節點並返回他們的值。保證二叉樹中結點的值各不相同。

給定一棵樹的根結點,請返回兩個調換了位置的值,其中小的值在前。


思路:

中序遍歷搜尋二叉樹的結果是升序的,比如1 2 3 4 5 6

兩個節點位置交換髮生錯誤,有兩種情況 1 5 3 4 2 6,有兩次降序的分別為5 3和2 6,錯誤節點是第一次降序的

max,和第二次降序的min;第二種情況1 2 4 3 5 6 ,有一次降序的即4 3,第一個錯誤是max=4,第二個是min=3.

綜合來看,就是找出中序遍歷結果中,第一次降序的max,和最後一次降序的min。


public class FindErrorNode {
    public int[] findError(TreeNode root) {
        // write code here
        if(root==null) return null;
        Stack<TreeNode> s=new Stack();
        int[] result=new int[2];
        result[0]=result[1]=-1;
        TreeNode p=root;
        TreeNode top=null;
        boolean first=true;
        while(!s.isEmpty()||p!=null){
            if(p!=null){
                s.push(p);
                p=p.left;
            }else{
                p=s.pop();
                if(top!=null&&top.val>p.val){//p為當前節點,top上一次為彈出的棧頂
                    result[1]=(result[1]==-1?top.val:result[1]);//結果輸出為最小值在前面
                    result[0]=p.val;
                }
               	top=p;
                p=p.right;
            }
        }
        return result;
    }
}


(3)

題目:

從二叉樹的節點A出發,可以向上或者向下走,但沿途的節點只能經過一次,當到達節點B時,路徑上的節點數叫作A到B的距離。對於給定的一棵二叉樹,求整棵樹上節點間的最大距離。給定一個二叉樹的頭結點root,請返回最大距離。保證點數大於等於2小於等於500.

思路:

理解題目的含義,對於一棵以root為根的二叉樹,樹上的最大距離可能來自3中情況:

情況1:完全來自root的左子樹,如圖所示,即最大路徑不經過root結點,只在結點1的左子樹2上面,此時的最大距離為8。

情況2:完全來自root結點的右子樹,如圖所示,最大路徑不經過root結點,只在結點1的右側左子樹3上面,此時最大距離是9。

情況3:來自結點root的兩側,如圖所示,經過root結點,此時的最大距離=root的左子樹的高度(即結點3的最長路徑)+root右子樹的高度(即結點3的最長路徑)+root結點本身。

分析可知,要計算結點root所在子樹的最長距離,需要已知:左子樹②的最長距離LMaxLength,左子樹的高度LHeight;右子樹③的最長距離RMaxLength,右子樹的高度RHeight.然後max(LMax,RMax,(LHeight+RHeight+1)),其最大值就是這棵二叉樹的最大距離,即對於每個子樹,需要求出它的最大距離和最大高度。

顯然這就是一個遞推關係式,可以使用遞迴來實現,即設計一個遞迴函式,給定一個根結點root,求出最大距離max和最大高度height並返回這2個數值。其中maxLength= Math.max(LMax,RMax,(LHeight+RHeight+1));而由於要返回這棵子樹的高度,如何求子樹的高度呢?二叉樹的高度就是它的2個子樹高度中的較大值再加上1,即height=Math.max(LHeight, RHeight)+1;

遞迴的遞推關係有了,關鍵是找到遞迴的起始條件或者理解為終止條件。這裡使用的思想是後序遍歷(先遍歷左右子樹再處理結論),聯絡二叉樹的後序遍歷演算法,進行改編。顯然if(root==null)時:max=0;height=0;

總結:設計一個遞迴函式,輸入根結點root,求出這棵二叉樹的最大距離maxLength和高度length並返回。遞推關係為:

maxLength= Math.max(LMax,RMax,(LHeight+RHeight+1));

height=Math.max(LHeight, RHeight)+1

邊界條件為:

if(root==null) return max=0;height=0;

在Java中不能分開返回2個值,因此要將2個值整合成為一個陣列進行返回即可

public class LongestDistance {
    public int findLongest(TreeNode root) {
        // write code here
        int[] lenAndHei=get(root);
        return lenAndHei[0];
    }
    
    public int[] get(TreeNode root){//遞迴求根為root的最大距離和高度
        int[] lenAndHei=new int[2];
        if(root==null){
            lenAndHei[0]=lenAndHei[1]=0;
            return lenAndHei;
        }
        
        //左子樹的最大距離和高度
        int[] left=get(root.left);
        int leftLen=left[0];
        int leftHei=left[1];
        
        //右子樹的最大距離和高度
        int[] right=get(root.right);
        int rightLen=right[0];
        int rightHei=right[1];
        
        //當前樹的最大距離和高度
        lenAndHei[0]=Math.max((Math.max(leftLen,rightLen)),(leftHei+rightHei+1));
        lenAndHei[1]=Math.max(leftHei,rightHei)+1;
        
        return lenAndHei;
    }
}


(4)

題目:

有一棵二叉樹,其中所有節點的值都不一樣,找到含有節點最多的搜尋二叉子樹,並返回這棵子樹的頭節點.給定二叉樹的頭結點root,請返回所求的頭結點,若出現多個節點最多的子樹,返回頭結點權值最大的。


思路:

題目(3)中每次遞迴呼叫時需要得到2個資訊(該子樹的最大長度,該子樹的高度)並將這2個資訊組成陣列進行返回,因此每次遞迴呼叫時先接收上次呼叫返回的陣列,然後進行處理,處理完的結果放入到一個新的陣列中再返回這個陣列,注意,(3)中需要收集返回的2個資訊都是int型別的,因此可以形成int[]陣列進行返回;

本題中由於業務邏輯更加複雜,每一次遞迴呼叫時需要利用的資訊有4個,同時遞迴方法執行結束後返回的資訊也有4個(

當前子樹的最大二叉搜尋子樹的根結點TreeNode,

最大搜尋子樹的結點數目int,

當前子樹的最小值int,

當前子樹的最大值int

由於這4個資訊不是同一型別的,因此不能組成一個陣列進行返回,所以對於TreeNode的資訊,依然可以直接返回,即令該遞迴函式的返回值是TreeNode,對於其他的3個int型別的引數

TreeNodelnode=postOrder(root.left,res);

intlmax=res[0];

intlmin=res[1];

intlnum=res[2];

TreeNodernode= postOrder(root.right,res);

intrmax=res[0];

intrmin=res[1];

intrnum=res[2];

例如,上面的res陣列可以是臨時建立的,也可以是使用的成員變數陣列,反正認為在TreeNode lnode=postOrder(root.left,res);呼叫結束後res就已經被賦值了,於是就可以取出res中的值進行使用了。Res陣列可以重複使用,因為res傳入給遞迴函式只是作為一個容器來讓方法填充資料的。

本題的業務邏輯:

快快快

要求以root為根的二叉樹中的最大搜尋二叉子樹的根結點,以root,root的左結點,root的右結點這個基礎結構來分析怎麼求二叉子樹的最大搜尋二叉樹。

顯然以root為根的二叉樹的最大搜尋子樹只可能來自2中情況:

情況1:當root左子樹root.left上的最大搜尋子樹就是left結點,並且root右子樹上root.right上的最大搜尋子樹就是right結點時,並且left子樹上的最大值<root.val<right子樹上的最小值,此時root結點就是這棵樹的最大搜尋子樹的根結點,返回root。

此時注意一種特殊情況如果root的某一個子節點不存在,例如當左結點不存在時,如果上面的條件依舊滿足,即right就是最大搜尋子樹,且root.val<right,那麼此時的最大搜尋子樹就是right子樹加上結點root;同理當right不存在,且left子樹就是左結點的最大搜尋子樹並且left<root.val,那麼此時的最大搜尋子樹就是left子樹加上root結點,這種情況應該作為left,right都存在的特殊情況一起考慮進去,直接返回root結點,因此採取的巧妙的策略就是當root==null時,令該子樹的結點數目為0,最小值為Integer.MAX_VALUE,於是顯然大於root結點;令最小值為Integer.MIN_VALUE,於是顯然小於root結點,並且返回null最為最大搜尋子樹的根結點。此時必然滿足情況1的判斷條件,於是會返回root作為搜尋子樹的根結點。

情況2:除此之外的情況都說明最大搜尋子樹只可能只出現在一邊在root的左子樹left或者右子樹right上而不可能跨越root的兩邊組成一棵最大搜尋樹。並且已知左子樹left上面搜尋子樹的根結點root1,其結點數目為n1;右子樹right上面搜尋子樹的結點為right,其結點數為n2;因此比較n1與n2的大小即可,取較大者對應的根結點進行返回即可。

public class MaxSubtree {
    public TreeNode getMax(TreeNode root) {
        // write code here
        if(root==null) return null;
        int[] res=new int[3];
        TreeNode searchNode=get(root,res);
        return searchNode;
    }
    
    public TreeNode get(TreeNode root,int[] res){//res[0]節點數目,res[1]root為根的樹的最小值,res[2]root為根的樹的最大值
        if(root==null){
            res[0]=0;
            res[1]=Integer.MAX_VALUE;
            res[2]=Integer.MIN_VALUE;
            return null;
        }
        
        //左子樹
        int[] left=new int[3];
        TreeNode leftNode=get(root.left,left);//遞迴左右子樹
        
        //右子樹
        int[] right=new int[3];
        TreeNode rightNode=get(root.right,right);
        
        //判斷兩種情況下的最大搜尋樹
        //情況一
        if(leftNode==root.left&&rightNode==root.right&&left[2]<root.val&&root.val<right[1]){
            //leftNode rightNode分別為root的左右孩子,且左孩子的最大值<root.val<右孩子的最小值(無重複值),此時root就是最大搜尋樹
            res[0]=left[0]+right[0]+1;
            //注意這裡不能這樣寫,因為要考慮左右子樹有一側為null的情況。
            /*
            res[1]=left[1];//最小值=左孩子的最小值
            res[2]=right[2];//最大值=右孩子的最大值
            */
            res[1]=Math.min(left[1],root.val);//左子樹可能為空
            res[2]=Math.max(right[2],root.val);//右子樹可能為空
            return root;
        }
        
        //情況二:節點多的子樹為root為根的最大搜尋子樹,最大搜尋子樹來自一邊
        res[0]=Math.max(left[0],right[0]);
        res[1]=Math.min(Math.min(left[1],right[1]),root.val);//root為根的子樹的最小值,不是最大搜尋樹的最小值
        res[2]=Math.max(Math.max(left[2],right[2]),root.val);//樹root的最大值
        
        return left[0]>right[0]?leftNode:rightNode;
    }
}



相關文章