二叉樹算法--簡單級別


二叉樹基礎框架

void traverse(TreeNode root){
    traverse(root.left);//遍歷左子樹
     traverse(root.right);//遍歷右子樹
}
//這個框架可以訪問到樹的每個節點
//相當一部分問題有對節點的操作,所以我們加入對節點操作的部分
void plusone(TreeNode root){
    if(root==null)return;//這個是剎車
    root.val=1;//這是對節點的操作
    plusOne(root.right);//驅動區域
    plusOne(root.left);//驅動區域
}

用到的題目
404.左葉子之和
54
108

Base模板升級

面試題68-II 二叉樹的最近公共祖先

image.png

遇見這個題其實只要稍作分析就知道.

總共有三種情況.


左右子樹各有一個節點
都在一個子樹中       ->   都在左子樹
          			->  都在右子樹

但是我們發現,要想實現這些內容似乎不能在節點操作區實現,因為一個節點根本無法判斷這些內容.這個跟之后的操作有很多的關系.那怎么辦呢?

答案如下:

class Solution {
   TreeNode node;
   public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
//遞歸內部操作區(指定剎車等等)
      if (root==null||root.val==p.val||root.val==q.val)return root;//找到就返回
       //遞歸驅動器
     TreeNode nodel= lowestCommonAncestor(root.left,p,q);
     TreeNode noder=lowestCommonAncestor(root.right,p,q);
     //遞歸結束操作區,對遞歸的值進行處理
     if (nodel==null){
         return noder;
     }//左邊不是返回右邊
     if (noder==null){
         return nodel;
     }//右邊也不是返回左邊
       return root;//左右都是返回根節點

   }
}

我們發現了我們將處理的方法放在了迭代去的下面

現在我們的模板又更新了

暫時為止的模板

class Solution {
    TreeNode node;
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {

        {剎車區:判定如何停止遞歸}
        ex:
        if (root == null) return 0;
        {節點操作區:對節點進行操作(可選)}
        {遞歸驅動區:用合理的操作讓遞歸進行||根據函數的返回值將兩個子樹返回值保存下來,讓他們們作為本函數的返回值}
        ex:
        int left_height = maxDepth(root.left);
        int right_height = maxDepth(root.right);
        
        
        //遞歸驅動區:一般來講遇見返回值為Boolean的問題,我們都要將遞歸驅動使用&&或||連接符連在一起,例子如下:
        iscure(left.left,right.right)&&iscure(left.right,right.left)
            //這個值將作為返回值.(平時會寫在遞歸返回區)
            //這是因為這樣可以將左右兩個子樹返回的值聯系在一起,作為root的最終值.
            //例子:965,112,671 
        //遞歸驅動區:遇見返回值為int的時候,你所應該考慮的是如何將每次遞歸產生的值傳遞到下一次遞歸中.其實和Boolean一樣,我們也需要一些手段將左子樹產生的值和右子樹聯系起來,最終形成對於root的操作.例子如下:
            Math.max(depth(root.left), depth(root.right)) + 1;
        //更多內容移步遞歸區代碼擴充--返回int
        
        //例題55-II
        {遞歸后操作區:對於遞歸驅動區產生的值進行處理}
        //列題:111,68-I,68-II
        {遞歸返回區:確定返回的內容,如果想返回的值和題目要求返回的值不一樣.就采用下面得戰術.}
        ex:
         return Math.min(a,b);
    }
}
//重寫一個函數讓題目給的函數調用他.
public boolean isSymmetric(TreeNode root) {
 
        return iscure(root.left,root.right);
    }
    
       private int iscure(TreeNode left,TreeNode right){

        return iscure(left.left,right.right)&&iscure(left.right,right.left);
    }

遞歸驅動區代碼擴充--返回int

遇見這種題一般有很多種可能,比較基礎的就是跟樹的深度有關.

對於這種問題其實很簡單,就是運用剎車和遞歸驅動區讓每次遞歸產生的值可以傳遞到下一次遞歸中.

 if (root==null) return 0;//剎車區域
//遞歸驅動區域
            a=isBalanced1(root.left);
            b=isBalanced1(root.right);
//遞歸返回區域
            return Math.max(a,b)+1;

我們發現,我們用在返回值上+1以及空節點返回值為0的手段,形成一個對於整個樹深度的遍歷.

然后用max函數將深度進行比較.也是將左右子樹聯系在一起.

當然,我么也可以在a和b上進行加減,然后返回.對於某些題是一種很好的解決方式.

(實際上,無論是Boolean還是int或者其他的,左右節點最終都要用一些辦法將她們連接在一起.)

相關的題目:
55-I,55-II,111,104,110

遞歸驅動區---擴展變種

這種題最核心的感覺就是兩棵樹的比較

面試28:

image.png

因為實際上最終解決問題的時候要對兩個樹進行操作,所以本質來講樹的參數需要的遍歷值時指數級上升的。如第一個二叉樹,一開始只要輸入root.leftroot.right兩個參數,到下一層就要輸入root.left.left,root.left.right,root.right.left,root.right.right以此類推,下面的數量會越來越多.

所以當你意識到,這種比較方式不可能通過手動實現的時候.那就要開始自己行動了.那就是擴展參數.

你會發現實際上,從第一層到第二層是從1個參數,變成2個參數.第二層到第三層將會是4個參數.

所以不斷地擴大參數是不現實的.

但是我們發現了一個規律,就是每層都比上次翻一番.這就是很好的解決辦法.

class Solution {
    public boolean isSymmetric(TreeNode root) {
   //一層變成二層
        return root==null?true:iscure(root.left,root.right);
    }
    
       public boolean iscure(TreeNode left,TreeNode right){
//處理部分已經刪除
        return iscure(left.left,right.right)&&iscure(left.right,right.left);
    }
}

如上方法實際上就是依靠遞歸部分連接在一起,形成對於參數的擴充.

如下是原版的模板,比較一下區別.

iscure(left.left);
iscure(left.right);
相關題目:
101:對稱二叉樹
572:另一顆子樹的子樹

中間的節點操作部分也有一個值得記住的地方

這個判斷方式可以輕松的發現左節點和右節點值不一樣的問題.

 if (left==null&&right==null)return true;
        if(left == null || right == null) return false;

這個判斷方式將是在之后經常出現的一種方式,先過濾掉左右都為空的情況.

剩下的情況就只剩下三種:

左空右有
左有右空
左有右有

所以完全可以用

if(left == null || right == null) return false;

來處理問題.

相關題目:

101:對稱二叉樹
572:另一顆子樹的子樹

以下是完整代碼

class Solution {
    public boolean isSymmetric(TreeNode root) {
        return root==null?true:iscure(root.left,root.right);
    }
       public boolean iscure(TreeNode left,TreeNode right){
        if (left==null&&right==null)return true;
        if(left == null || right == null || left.val != right.val) return false;
        return iscure(left.left,right.right)&&iscure(left.right,right.left);
    }
}

運用到集合的題目

這些題目難度相對來說要大上一些,一般來講運用到集合就是為了回溯.我們知道在遞歸的函數中是無法訪問自己的根節點的,那么最好訪問的辦法就是將根節點的內容記錄下來(放入容器中),讓它傳到參數中,或讓其作為類的共有節點,將各個節點的值遞歸的加入集合之中.

例如:653

class Solution {
    public boolean findTarget(TreeNode root, int k) {
        List<Integer>list=new ArrayList<>();
        return find(root, k, list);
    }
    private static boolean find(TreeNode root,int k,List<Integer> list){
        if (root==null)return false;
        if (list.contains(k-root.val)){//因為要求兩數之和,所以直接用總數減去這個節點,剩下的值如果存在之前的值中,那就是可以的.
            return true;
        }
        list.add(root.val);//將每個遍歷過的節點放進list中.
       return find(root.right,k,list)||find(root.left,k,list);
    }
    
   
}

如上是比較簡單的記錄路徑--被我稱作單層集合,就是只用一個集合記錄即可

公式為如下

 List<Integer>list=new ArrayList<>();//聲明一個集合

//傳入之后的遞歸函數中
private static boolean find(TreeNode root,int k,List<Integer> list)

還有復雜的,就是用到兩個集合,被我曾作雙層集合.

這種集合也往往和迭代方式混用.

如:32-II

 class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        Queue<TreeNode> queue = new LinkedList<>();//一個集合傳入要處理的節點
        List<List<Integer>> res = new ArrayList<>();//一個集合傳入處理后的數據
        if(root != null) queue.add(root);//將根節點加入
        while(!queue.isEmpty()) {
            List<Integer> tmp = new ArrayList<>();
            for(int i = queue.size(); i > 0; i--) {
                TreeNode node = queue.poll();//退出一個處理集合中的節點
                //對其進行處理
                tmp.add(node.val);//將處理后的內容放入另一個集合中
                if(node.left != null) queue.add(node.left);//驅動
                if(node.right != null) queue.add(node.right);//驅動
            }
            res.add(tmp);
        }
        return res;
    }
}//相關題目107,589,590

相關題目

653,32-II,
1022<-本題有些特殊,真正作為容器的是一個字符串,將字符串放進容器中.
107
700,938<-這個使用的是迭代
437
589
590

縮減代碼量

{
    if(s.val!=t.val)return false;//不涉及到剎車的部分可以直接寫道return中
   return 
       isSubtree1(s.left,t.left)
       &&
       isSubtree1(s.right,t.right);
}
{
return 
    t1.val == t2.val 
    && 
    equals(t1.left, t2.left)
    && 
    equals(t1.right, t2.right);
}

這個內容是對上文的一個解釋,關於如何將左右子樹得出的結論聯系在一起的

{
      int left_height = maxDepth(root.left);
      int right_height = maxDepth(root.right);
      return java.lang.Math.max(left_height, right_height) + 1;
}
{
    return  max=1+Math.max(maxDepth(root.left),maxDepth(root.right));

}

 //遞歸驅動區:一般來講遇見返回值為Boolean的問題,我們都要將遞歸驅動使用&&或||連接符連在一起,例子如下:
        iscure(left.left,right.right)&&iscure(left.right,right.left)
            //這個值將作為返回值.(平時會寫在遞歸返回區)
            //這是因為這樣可以將左右兩個子樹返回的值聯系在一起,作為root的最終值.
            //例子:965,112
        //遞歸驅動區:遇見返回值為int的時候,你所應該考慮的是如何將每次遞歸產生的值傳遞到下一次遞歸中.其實和Boolean一樣,我們也需要一些手段將左子樹產生的值和右子樹聯系起來,最終形成對於root的操作.例子如下:
            Math.max(depth(root.left), depth(root.right)) + 1;
        //更多內容移步遞歸區代碼擴充--返回int


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM