樹 – 遞歸部分
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;
}
}