LeetCode刷題(二):樹的遞歸部分

樹 – 遞歸部分

1.求樹的高度 – :

  • 沒什麼難度,分別對左子樹和右子樹求高度,取兩者的較大值。
  • +1是爲了囊括結點本身的高度
public class MaxDepth_104 {
    public int maxDepth(TreeNode root) {
        if (root == null) return 0;
        // 分別遞歸求解左右子樹的高度,注意最後需要加上結點本身的高度
        return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
    }
}

2.判斷一個樹是否是平衡樹

  • 當需要進行遞歸的值與結果值不同時,可以考慮通過一個成員變量作爲結果

  • 建立一個額外的方法來實現遞歸,主方法用於返回結果

  • 在額外方法的某個判定條件中,會對成員變量的值進行更改

public class IsBalanced_110 {

    // 難點:沒有考慮可以通過一個成員變量來作爲結果
    private boolean result = true;

    public boolean isBalanced(TreeNode root) {
        maxDepth(root);
        return result;
    }

    public int maxDepth(TreeNode root) {
        if (root == null) return 0;
        // 形成有效遞歸
        int l = maxDepth(root.left);
        int r = maxDepth(root.right);
        // 存在不合理則將最後的結果置爲false,否則繼續完成遞歸
        if (Math.abs(l - r) > 1) result = false;
        return 1 + Math.max(l, r);
    }
}

3.兩節點的最長路徑

  • 遞歸的中間值與答案要求的不同,依然考慮用一個成員變量來替代
  • 不要求從根節點向下,那麼路徑應該是左右滿足條件的路徑之和
  • 這裏長度是指邊長,而L + R + 1是節點的個數,要求邊的數量還需要-1
public class DiameterOfBinaryTree_543 {

    private int ans;

    public int diameterOfBinaryTree(TreeNode root) {
        depth(root);
        return ans;
    }

    private int depth(TreeNode root) {
        if (root == null) return 0;
        // 分別遞歸求解左右子樹的深度
        int L = depth(root.left);
        int R = depth(root.right);
        // 核心如果是經過了根節點,其實求的就是根節點兩條路徑的深度之和
        // 加一是節點總數爲L+R+1,-1是因爲要求的是邊數,所以減一
        ans = Math.max(L + R + 1 - 1, ans);
        // 還要加上結點本身
        return Math.max(L, R) + 1;
    }
}

4.翻轉樹

  • 如果要交換左右子樹,通過一個啞結點作爲副本,這樣可以不改變原樹的結構
  • 而左子樹與右子樹對調,只需要將以前的右半部分賦值給新的左半部分即可
public class InvertTree_226 {
    public TreeNode invertTree(TreeNode root) {
        if (root == null) return null;
        TreeNode dummy = new TreeNode(root.val);
        // 核心是通過遞歸方法,將以前的右子樹,賦值給新的左子樹
        dummy.left = invertTree(root.right);
        dummy.right = invertTree(root.left);
        return dummy;
    }
}

// 另一種解法, 思想差不多,就是對改變之前的樹做一個備份
public TreeNode invertTree(TreeNode root) {
    if (root == null) return null;
    TreeNode left = root.left;  // 後面的操作會改變 left 指針,因此先保存下來
    root.left = invertTree(root.right);
    root.right = invertTree(left);
    return root;
}

5.歸併兩棵樹

  • 遞歸合併的終止條件需要滿足
  • 這裏爲了不改變原有樹的結構,依然是通過創建一個新的樹節點來實現

可以歸納一個結論 – 當不希望改變樹的結構時,往往通過創建一個新的結點。然後修改並返回這個臨時結點

public class MergeTrees_617 {
    public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
        // 合併的遞歸終止條件
        if (t1 == null && t2 == null) return null;
        if (t1 == null) return t2;
        if (t2 == null) return t1;
        // 當前結點的值是兩個結點值相加
        TreeNode node = new TreeNode(t1.val + t2.val);
        // 樹的合併是一一對應合併的
        node.left = mergeTrees(t1.left, t2.left);
        node.right = mergeTrees(t1.right, t2.right);
        return node;
    }
}

6.判斷路徑和是否等於一個數

  • 常規方法是按照之前的思路,要求的值和需要進行遞歸的方式不匹配,那麼構造一個新的遞歸方法,並用成員變量作爲結果
  • 注意根節點的判定方法 – 向下依次減少,當sum == 0時,發現剛好減到0,那麼可以對result置爲true。同時,問題是一個存在性問題,那麼只需要有一個結點滿足即可
  • 第二種解法帶有一點離散數學的思想,詳見註釋。最後返回的遞歸結果是一個析取關係
public class HasPathSum {

    private boolean result = false;

    public boolean hasPathSum1(TreeNode root, int sum) {
        search(root, sum);
        return result;
    }

    // 常規方法,構建一個額外的遞歸
    public void search(TreeNode root, int sum) {
        if (root == null) return;
        sum -= root.val;
        if (sum == 0 && root.left == null && root.right == null)
            result = true;
        search(root.left, sum);
        search(root.right, sum);
    }

    // 簡化版。通過離散數學關係分析,這是一個析取關係,只要有一個爲true,則結果爲true
    public boolean hasPathSum(TreeNode root, int sum){
        // 通過false終止遞歸
        if(root == null) return false;
        // 這裏是與root val相等,而不是0,注意
        if(sum == root.val && root.left == null && root.right == null) 
            return true;
        // 結果爲析取形式,只有一條路徑滿足長度需求,那麼可以得到結果是正確的
        return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val);
    }

}

7.求路徑和

  • 這裏引入了一種新的方法 – 就是對根節點做特殊遞歸,然後對左右子樹做一般遞歸,再將結果進行歸併 (我命名爲根節點特殊化)
  • 特殊化的原因是:除了根本身,其子樹可能也會存在滿足題目條件的情況,因此需要分開考慮
  • 其餘注意事項見答案
public class PathSum_437 {

    public int pathSum(TreeNode root, int sum) {
        if (root == null) return 0;
        /**
         * 1.以根節點爲root求存在的分支數
         * 2.分別將左子樹和右子樹求其滿足的路徑數,相加求總和
         */
        int ret = pathSumStartWithRoot(root, sum) + pathSum(root.left, sum) + pathSum(root.right, sum);
        return ret;
    }

    private int pathSumStartWithRoot(TreeNode root, int sum) {
        // root爲空時,說明沒有路徑
        if (root == null) return 0;
        // 如果與sum相等,那麼證明找到了一條,否則爲0
        int ret = root.val == sum ? 1 : 0;
        // 分別記錄左右子樹作爲根節點時向下遍歷的路徑條數
        // 關鍵點 --- 假定存在,那麼向下遍歷時則需要在此基礎上減去對應值
        ret += pathSumStartWithRoot(root.left, sum - root.val) + pathSumStartWithRoot(root.right, sum - root.val);
        return ret;
    }

    public int pathSum1(TreeNode root, int sum) {
        return pathSum(root, sum, new int[1000], 0);
    }

    // 第二種解法!! 有待理解
    public int pathSum(TreeNode root, int sum, int[] array/*保存路徑*/, int p/*指向路徑終點*/) {
        if (root == null) {
            return 0;
        }
        int tmp = root.val;
        int n = root.val == sum ? 1 : 0;
        for (int i = p - 1; i >= 0; i--) {
            tmp += array[i];
            if (tmp == sum) {
                n++;
            }
        }
        array[p] = root.val;
        int n1 = pathSum(root.left, sum, array, p + 1);
        int n2 = pathSum(root.right, sum, array, p + 1);
        return n + n1 + n2;
    }

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章