1. 程式人生 > >二叉樹的路徑處理問題

二叉樹的路徑處理問題

路徑處理問題,實質上是使用樹的遍歷方法對樹進行遍歷,常常採用樹的先序遍歷方法對樹進行遍歷,對樹的先序遍歷方法稍加改動,改動先序遍歷時對中間節點的處理邏輯和對左右子樹的處理邏輯來實現題目的要求。

  1. 處理從根節點到每一個葉子節點的路徑(即該二叉樹的所有路徑)上的所有節點,

         例如leetcode257,問題描述及程式碼如下:

         問題描述:

         給定一個二叉樹,返回所有從根節點到葉子節點的路徑。

         說明: 葉子節點是指沒有子節點的節點。

         示例:

         輸入:

             1
           /   \
         2     3
           \
             5

         輸出: ["1->2->5", "1->3"]

         解釋: 所有根節點到葉子節點的路徑為: 1->2->5, 1->3

         解決思路:從根節點開始進行中序遍歷,先處理中序遍歷時當前節點,將其入字串,字串中儲存著當前路徑中到目前為止的所有節點,然後處理左右子樹,在處理左右子樹時要判斷是否已經為葉子節點,如果已經到達葉子節點,則將當前字串入線性表,否則分別判斷左右子樹是否為空,遞迴處理其中不為空的子樹。

         程式碼如下:字串用於儲存當前路徑,即從根節點開始到當前路徑的目前節點的所有節點,線性表用於儲存所有的路徑,每一條路徑線上性表中為一個字串,String物件.trim() 返回當前字串的去除頭尾空白後的副本。

class Solution {
    //先序遍歷樹,先處理當前節點,然後遍歷左右子樹,依次處理,加入list中
    //而且左右子樹需要分開加入
    public List<String> binaryTreePaths(TreeNode root) {
        List<String> list = new ArrayList<>();
        String s = "";
        paths(root, list, s);
        return list;
    }
    private void paths(TreeNode root, List<String> list, String s){
        //到達葉節點時停止遞迴
        if(root == null){
            return;
        }
        //先序遍歷,處理中間節點
        //要判斷是否已經到達一條路徑的盡頭,並在到達時將字串入線性表
        //實質上還是對先序遍歷的稍加改動
        s += root.val + " ";
        if(root.left == null && root.right == null){
            list.add(s.trim().replace(" ", "->"));
        }
        //先序遍歷,處理左右子樹
        //處理邏輯為,左右子樹中任何一個不為空,便遞迴處理該子樹
        if(root.left != null){
            paths(root.left, list, s);
        }
        if(root.right != null){
            paths(root.right, list, s);
        }
    }
}

2.leetcode 112路徑總和

問題描述:

給定一個二叉樹和一個目標和,判斷該樹中是否存在根節點到葉子節點的路徑,這條路徑上所有節點值相加等於目標和。

說明: 葉子節點是指沒有子節點的節點。

示例: 
給定如下二叉樹,以及目標和 sum = 22

              5
             / \
            4   8
           /   / \
          11  13  4
         /  \      \
        7    2      1

返回 true, 因為存在目標和為 22 的根節點到葉子節點的路徑 5->4->11->2

解決思路:寫一個方法,該方法通過先序遍歷統計根節點到葉子節點的每一條路徑上節點和值等於目的值的數量,注意是從根節點開始,到葉子節點結束的所有路徑。

程式碼:

class Solution {
    //根節點到葉子節點
    public boolean hasPathSum(TreeNode root, int sum) {
        if(root == null){
            return false;
        }
        return (path(root, sum)>0) ? true : false;
    }
    private int path(TreeNode root, int sum){
        //遞迴終止條件
        if(root == null){
            return 0;
        }
        //先序遍歷,先處理中間節點
        //關於result在這裡宣告的問題,需要說明一下,多多體會遞迴時在遞迴方法中宣告的變數所起到的所用
        //result在遞迴方法中宣告,然後每次遞迴呼叫方法處理左右子樹返回的值都給result,方法最後返回result
        //每次宣告的result都只在當前遞迴呼叫層有效,但由於將遞迴呼叫的方法返回值付給了當前層的reuslt
        //這就使得最外層的result得到了層層往裡的遞迴呼叫的方法的返回值
        //也可以反過來考慮,從葉子節點開始看,或者從找到的那個和為目的和的路徑的終節點看,該終結點返回result為1
        //上一層的result會加上該終結點統計得到的1
        //層層上傳,使得最終的result是1
        int result = 0;
        if(root.left == null && root.right == null && sum == root.val){
            result ++;
        }
        //先序遍歷,後遞迴處理左右子樹
        //注意,此處將題目要求的求和變成,每次判斷當前節點值是否等於目的和值的減去前面所有節點和和值
        //將求和變成減法
        //sum - root.val
        if(root.left != null){
            result += path(root.left, sum-root.val);
        }
        if(root.right != null){
            result +=path(root.right, sum-root.val);
        }
        
        return result;
    }
}

3.leetcode 437路徑總和lll

問題描述:

給定一個二叉樹,它的每個結點都存放著一個整數值。

找出路徑和等於給定數值的路徑總數。

路徑不需要從根節點開始,也不需要在葉子節點結束,但是路徑方向必須是向下的(只能從父節點到子節點)。

二叉樹不超過1000個節點,且節點數值範圍是 [-1000000,1000000] 的整數。

示例:

root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8

      10
     /  \
    5   -3
   / \    \
  3   2   11
 / \   \
3  -2   1

返回 3。和等於 8 的路徑有:

1.  5 -> 3
2.  5 -> 2 -> 1
3.  -3 -> 11

解決思路:因為不要求起始節點為根節點,因此需要遍歷每一個節點,從每一個節點開始計算當前節點下的所有路徑值和是否為目的值,由於樹的遞迴操作的遍歷行,遍歷每一個節點時,可以採用遞迴,先處理中間節點,然後遞迴處理左右子樹。需要寫一個方法,該方法接受一個節點、目的和值作為引數,返回該節點作為起始位置下和值為目的和值的路徑數。

程式碼:

class Solution {
    //統計從每一個節點開始的所有路徑求和是否等於目的值
    //考慮到樹的特殊性,在遍歷統計每一個節點時,不需要使用for迴圈,考慮樹的遞迴遍歷,只需要每次處理中間節點
    //並遞迴呼叫處理其左右子樹
    public int pathSum(TreeNode root, int sum) {
        //防止root.left或者root.right出現NullPointerException,需要判斷當前節點是否為空
        if(root == null){
            return 0;
        }
        return path(root, sum) + pathSum(root.left, sum) + pathSum(root.right, sum);
    }
    //path先序遍歷
    //path方法傳入當前節點及目的和值,判斷從當前節點開始有多少條路徑的和值等於目的值sum
    private int path(TreeNode root, int sum){
        //遞迴至葉節點返回條件
        if(root == null){
            return 0;
        }
        //先序遍歷,先處理當前節點
        int result = 0;
        if(root.val == sum){
            result++;
        }
        //先序遍歷,處理完當前節點後,處理左右子樹
        result += path(root.left, sum - root.val);
        result += path(root.right, sum - root.val);
        
        return result;
    }
}

4.leetcode687題

問題描述:

給定一個二叉樹,找到最長的路徑,這個路徑中的每個節點具有相同值。 這條路徑可以經過也可以不經過根節點。

注意:兩個節點之間的路徑長度由它們之間的邊數表示。

示例 1:

輸入:

              5
             / \
            4   5
           / \   \
          1   1   5

輸出:

2

示例 2:

輸入:

              1
             / \
            4   5
           / \   \
          4   4   5

輸出:

2

注意: 給定的二叉樹不超過10000個結點。 樹的高度不超過1000。

解決思路:

思路一,先序遍歷,最容易想到的就是先序遍歷,或許是先序遍歷用習慣了?先寫一個方法,該方法接受一個節點作為引數,並計算出以該引數節點作為根節點的樹下,經過根節點的最長同值路徑,三種情況,左右子樹加根節點組成最長同值路徑,左子樹加根節點組成最長同值路徑,右子樹加根節點組成最長同值路徑;然後再另外一個方法中遍歷這棵樹,在訪問每一個節點時,呼叫上述方法,並傳入當前節點作為引數,計算當前節點下且經過當前節點的最長同值路徑,儲存遍歷過程中得到的最長同值路徑。寫出了上述思路的實現過程,但總是有測試用例出錯誤,排查了一上午也沒解決,上述思路的邏輯判斷過多,出錯誤後難以排查解決。

實際上,在做這道題時,產生上述思路的原因是習慣於使用先序遍歷,當先序遍歷不好解決問題的時候,應當考慮中序遍歷,和後序遍歷,一定要多思考先序、中序、後序遍歷的區別,及三種遍歷各自的遍歷路徑和處理順序。

思路二,後序遍歷,先處理左右子樹,然後處理中間節點,在處理中間節點的時候,判斷左右子樹的值是否與中間節點的值相同,若相同,則同值路徑加長,不相同,則說明左右子樹的同值路徑無法與中間節點合併加長,故清零;由於是後序遍歷,因此處理順序是從最底層開始向上層層處理。這個思路的處理方式是從下往上找同值路徑,並判斷左右子樹能否與當前連線成更大的同值路徑,已經連線好的下層同值路徑長度儲存在result中,在和中間節點連線新的同值路徑時,左右子樹的同值路徑必須是單向路徑(即左右子樹中的同值路徑不會是左中右這麼連線的),因此find返回值為left與right中較大的,即左右子樹中同值路徑較長的。

class Solution {
    public int longestUnivaluePath(TreeNode root) {
        find(root);
        
        return result;
    }
    int result = 0;
    private int find(TreeNode root){
        if(root == null)
            return 0;
        
        int left = find(root.left);
        int right = find(root.right);
        
        if(root.left != null && root.val == root.left.val)
            left++;
        else
            left = 0;
        
        if(root.right != null && root.val == root.right.val)
            right++;
        else
            right = 0;
        
        result = Math.max(result, left+right);
        
        return Math.max(left, right);
    }
}