樹是天生的適合遞歸的數據結構,很多樹的問題用遞歸都可以非常漂亮的解決,而迭代往往比較複雜。熟練使用遞歸解決下面的二十多個二叉樹問題,就能更進一步掌握遞歸。
文章目錄
普通二叉樹
104.二叉樹的最大深度
子問題:左右子樹中較高的高度作爲當前樹的高度
public class Solution {
public int maxDepth(TreeNode root) {
return root == null ? 0 : Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
}
}
111.二叉樹的最小深度
與二叉樹的最大深度有所不同,如果子樹爲空,最小深度不爲1,因爲深度定義是到葉子節點的,所以需要處理子樹爲空的情況
public class Solution {
public int minDepth(TreeNode root) {
//空樹返回0
if (root == null) return 0;
//一邊樹爲空返回非空樹高度+1
else if (root.left == null && root.right != null) return minDepth(root.right) + 1;
else if (root.left != null && root.right == null) return minDepth(root.left) + 1;
//子樹均爲空或均不爲空返回子樹最小高度+1
else return Math.min(minDepth(root.left), minDepth(root.right)) + 1;
}
}
110.平衡二叉樹
自頂向下
子問題:當前樹的左右子樹高度差小於2,且左右子樹都是平衡二叉樹
public class Solution {
public boolean isBalanced(TreeNode root) {
if (root == null) return true;
//如果左右節點高度差小於1,且左右節點都是滿足平衡的
return Math.abs(maxDepth(root.left) - maxDepth(root.right)) < 2
&& isBalanced(root.left)
&& isBalanced(root.right);
}
private int maxDepth(TreeNode node) {
return node == null ? 0 : Math.max(maxDepth(node.left), maxDepth(node.right)) + 1;
}
}
自底向上
子問題:獲取當前樹的左右子樹的高度,如果左右子樹高度差大於1,設置全局的返回值爲false,然後返回樹的高度
因爲壓棧到葉子節點纔開始返回,所以是自底向上的,省去了多次獲取樹的高度
public class Solution {
private boolean res = true;
public boolean isBalanced(TreeNode root) {
maxDepth(root);
return res;
}
private int maxDepth(TreeNode root) {
if (root == null) return 0;
//獲取左右子樹高度
int left = maxDepth(root.left);
int right = maxDepth(root.right);
//如果發現不是平衡的了就設置爲false;
if (Math.abs(left - right) > 1) res = false;
//返回樹的高度
return Math.max(left, right) + 1;
}
}
543.二叉樹的直徑(兩節點最大路徑)
題意解釋:
在二叉樹中兩個節點可以構成的最長路徑
可以不通過根節點的情況
子問題:計算每棵子樹的左右子樹高度之和,最大值就是整棵樹的最大值
和自底向上的求二叉樹的高度的代碼一致,只是每次獲取左右子樹高度後計算一個當前最大左右子樹和
public class Solution {
private int max = 0;
public int diameterOfBinaryTree(TreeNode root) {
dfs(root);
return max;
}
private int dfs(TreeNode root) {
if (root == null) return 0;
//獲取左右子樹的高度
int lefth = dfs(root.left);
int righth = dfs(root.right);
//獲取左右子樹和的最大值
max = Math.max(max, lefth + righth);
//返回樹的高度
return Math.max(lefth, righth) + 1;
}
}
687.最長同值路徑
思路同上一個題兩節點最大路徑,自底向上
子問題:求和當前值相等的左右子樹的長度的和
public class Solution {
private int path = 0;
public int longestUnivaluePath(TreeNode root) {
dfs(root);
return path;
}
private int dfs(TreeNode root) {
if (root == null) return 0;
//和左子樹值相同的串的長度
int left = dfs(root.left);
//和右子樹值相同的串的長度
int right = dfs(root.right);
//和當前值相同的左子樹長度
int leftPath = root.left != null && root.val == root.left.val ? left + 1 : 0;
//和當前值相同的右子樹長度
int rightPath = root.right != null && root.val == root.right.val ? right + 1 : 0;
//比較當前節點的路徑更新最長路徑
path = Math.max(path, leftPath + rightPath);
//返回當前節點的最長串長度
return Math.max(leftPath, rightPath);
}
}
226.翻轉二叉樹
子問題:獲取左子樹和右子樹,將左子樹賦值給root.tight,右子樹賦值給root.left,實現左右子樹交換
public class Solution {
public TreeNode invertTree(TreeNode root) {
if (root == null) return null;
TreeNode left = invertTree(root.left);
TreeNode right = invertTree(root.right);
root.left = right;
root.right = left;
return root;
}
}
101.對稱二叉樹
子問題:判定左子樹子左節點和右子樹的右節點是否相等,判斷左子樹的右節點和右子樹的左節點是否相等
public class Solution {
public boolean isSymmetric(TreeNode root) {
if (root == null) return true;
return helper(root.left, root.right);
}
private boolean helper(TreeNode left, TreeNode right) {
if (left == null && right == null) return true;
if (left == null || right == null) return false;
if (left.val != right.val) return false;
return helper(left.left, right.right) && helper(left.right, right.left);
}
}
617.合併二叉樹
子問題:
- 如果兩個樹的子樹都爲null,返回null
- 如果一個樹的子樹爲空,返回另一個樹的子樹
- 如果兩個樹的子樹都不爲空,返回兩棵樹節點值相加的新的節點,新節點的左右子樹遞歸創建
public class Solution {
public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
//如果兩個樹的子樹都爲null,返回null
if (t1 == null && t2 == null) return null;
//如果一個樹的子樹爲空,返回另一個樹的子樹
if (t1 == null) return t2;
if (t2 == null) return t1;
//如果兩個樹的子樹都不爲空,返回兩棵樹節點值相加的新的節點,新節點的左右子樹遞歸創建
TreeNode root = new TreeNode(t1.val + t2.val);
root.left = mergeTrees(t1.left, t2.left);
root.right = mergeTrees(t1.right, t2.right);
return root;
}
}
112.路徑總和
子問題:
判斷當前節點是不是葉子節點且sum-葉子節點的值
爲0,滿足條件則存在路徑
如果不符合就繼續看左右節點符不符合,sum的值變爲減去當前節點值的新的值
public class Solution {
public boolean hasPathSum(TreeNode root, int sum) {
//終止條件
if (root == null) return false;
//符合的葉子節點
if (root.left == null && root.right == null && sum - root.val == 0) return true;
//不符合的情況
return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val);
}
}
437.路徑總和III
子問題:求以當前節點爲起始的滿足的次數和以左右節點爲起始滿足的次數的和
public class Solution {
public int pathSum(TreeNode root, int sum) {
//終止條件
if (root == null) return 0;
//返回以當前節點爲root節點滿足的次數+左節點爲root滿足的次數+右節點爲root滿足的次數
return pathSumStartWithRoot(root, sum)
+ pathSum(root.left, sum)
+ pathSum(root.right, sum);
}
//計算以root爲起始點滿足的情況數
private int pathSumStartWithRoot(TreeNode root, int sum) {
if (root == null) return 0;
int ret = 0;
if (root.val == sum) ret++;
ret += pathSumStartWithRoot(root.left, sum - root.val)
+ pathSumStartWithRoot(root.right, sum - root.val);
return ret;
}
}
對於可以從樹的任意點爲起始點的問題,調用兩個遞歸函數。代碼模板:437和572兩個題都是類似的
public int mainMethods(TreeNode root){
//退出條件
if (root == null)return 0;
//調用helper函數完成從本節點開始的任務
helper(root);
mainMethods(root.left);
mainMethods(root.right);
return //具體需要的操作
}
//業務操作
private void helper(TreeNode root) {
}
572.另一個樹的子樹
子問題:層層下探,將每個節點作爲t的根節點進行遞歸比對,如果出現s和t同時爲null,說明匹配成功
public class Solution {
public boolean isSubtree(TreeNode s, TreeNode t) {
if (s == null) return false;
//以s的每個節點作爲子樹的根節點與t進行遞歸比對,只要有一個true就算成功了
return isSubtreeWithRoot(s, t) || isSubtree(s.left, t) || isSubtree(s.right, t);
}
private boolean isSubtreeWithRoot(TreeNode s, TreeNode t) {
//同時爲空,說明比對完成了,剛好是子樹
if (t == null && s == null) return true;
//只有一個爲空,說明數不完全相同,有一邊少了
if (t == null || s == null) return false;
//值不相同,匹配失敗
if (t.val != s.val) return false;
//匹配成功當前節點,繼續遞歸下探左右節點
return isSubtreeWithRoot(s.left, t.left) && isSubtreeWithRoot(s.right, t.right);
}
}
404.左葉子之和
子問題:遞歸訪問左子樹和右子樹,如果左子樹爲葉子節點就返回左子樹的值
public class Solution {
public int sumOfLeftLeaves(TreeNode root) {
if (root == null) return 0;
//判定左節點是否是葉子節點,則不用遞歸左子樹,直接返回葉子節點值
if (isleaf(root.left)) return root.left.val + sumOfLeftLeaves(root.right);
return sumOfLeftLeaves(root.left) + sumOfLeftLeaves(root.right);
}
private boolean isleaf(TreeNode node) {
if (node == null) return false;
return node.left == null && node.right == null;
}
}
public class Solution {
public int sumOfLeftLeaves(TreeNode root) {
return root == null ? 0 : ((root.left == null ? false : (root.left.left == null && root.left.right == null)) ? root.left.val : sumOfLeftLeaves(root.left)) + sumOfLeftLeaves(root.right);
}
}
337.打家劫舍III
子問題:選擇當前層+下兩層的值還是選擇下一層的值
public class Solution {
public int rob(TreeNode root) {
if (root == null) return 0;
int val1 = root.val;
if (root.left != null) val1 += rob(root.left.left) + rob(root.left.right);
if (root.right != null) val1 += rob(root.right.left) + rob(root.right.right);
int val2 = rob(root.left) + rob(root.right);
return Math.max(val1, val2);
}
}
671.二叉樹中第二小的節點
子問題:查看當前節點的子節點值是否大於節點值,如果不是,就遞歸查找大於的值返回
public class Solution {
public int findSecondMinimumValue(TreeNode root) {
//如果當前節點是空節點,或者當前節點的下一層是空節點,則說明沒找到,返回-1
if (root == null || root.left == null || root.right == null) return -1;
//獲取左右子節點的值
int leftv = root.left.val;
int rightv = root.right.val;
//如果子節點的值和當前節點相等,就下探找子樹中符合的節點
if (root.val == leftv) leftv = findSecondMinimumValue(root.left);
if (root.val == rightv) rightv = findSecondMinimumValue(root.right);
//如果兩個子樹都找到了大於根節點的值,返回最小值
if (leftv != -1 && rightv != -1) return Math.min(leftv, rightv);
//如果只有左子樹存在就返回左子樹的
if (leftv != -1) return leftv;
//如果只有右子樹存在就返回右子樹的
return rightv;
}
}
BST
669.修剪二叉搜索樹
子問題:
- 如果根節點值大於R,說明新樹都在左子樹,修剪左子樹
- 如果根節點值小於L,說明新樹都在右子樹,修剪右子樹
- 如果根節點值位於[L,R],修剪左右子樹後返回根節點
public class Solution {
public TreeNode trimBST(TreeNode root, int L, int R) {
if (root == null) return root;
//新樹在根節點左邊
if (root.val > R) return trimBST(root.left, L, R);
//新樹在根節點右邊
if (root.val < L) return trimBST(root.right, L, R);
//新樹包含了根節點,左右修剪返回根節點
root.left = trimBST(root.left, L, R);
root.right = trimBST(root.right, L, R);
return root;
}
}
230.二叉搜索樹中第K小的元素
中序遍歷:利用BST中序遍歷結果是升序的特點
public class Solution {
private int count = 0;
private int val;
public int kthSmallest(TreeNode root, int k) {
search(root, k);
return val;
}
private void search(TreeNode root, int k) {
if (root == null) return;
search(root.left, k);
count++;
if (count == k) {
val = root.val;
return;
}
search(root.right, k);
}
}
遞歸:
子問題:
統計左子樹節點個數,如果節點數+1等於k則找到值,如果不等於就向左右子樹遞歸
public class Solution {
public int kthSmallest(TreeNode root, int k) {
if (root.left == null) return kthSmallest(root.right, k);
//統計左子樹個節點數
int leftcount = count(root.left);
//如果左子樹節點+1等於k,則說明根節點就是需要找的
if (leftcount + 1 == k) return root.val;
//如果左子樹節點+1大於k,則說明需要找的節點再根節點左邊,調用左節點
if (leftcount + 1 > k) return kthSmallest(root.left, k);
//如果左子樹節點+1小於k,則說明需要找的節點再根節點右邊,調用右節點,更新k的值爲減去了左子樹節點加根節點的數目
return kthSmallest(root.right, k - leftcount - 1);
}
//統計這棵樹的非空節點個數
private int count(TreeNode root) {
return root == null ? 0 : 1 + count(root.left) + count(root.right);
}
}
538.把二叉搜索樹轉換爲累加樹
利用BST中序遍歷是升序的基礎,如果交換遍歷順序就是降序遍歷,只需要一個全局變量暫存之前的值就可以做到累加
public class Solution {
private int sum = 0;
public TreeNode convertBST(TreeNode root) {
search(root);
return root;
}
private void search(TreeNode root) {
if (root == null) return;
search(root.right);
sum += root.val;
root.val = sum;
search(root.left);
}
}
235.二叉搜索樹的最近公共祖先
子問題:
- 如果兩個節點值都小於根節點,說明公共祖先在左子樹
- 如果兩個節點值都大於根節點,說明公共祖先在又子樹
- 其他情況,公共祖先就是根節點
public class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
//兩個節點都小於根節點,訪問左節點
if (p.val < root.val && q.val < root.val) return lowestCommonAncestor(root.left, p, q);
//兩個節點都大於根節點,訪問右節點
if (p.val > root.val && q.val > root.val) return lowestCommonAncestor(root.right, p, q);
//兩個節點有一個等於根節點或者一個大於根節點一個小於根節點,根節點都符合公共祖先定義
else return root;
}
}
98.驗證二叉搜索樹
public class Solution {
long last = Long.MIN_VALUE;
public boolean isValidBST(TreeNode root) {
if (root == null) return true;
//遍歷左節點,如果無序返回falsae
if (!isValidBST(root.left)) return false;
//當前層不是有序的
if (root.val <= last) return false;
last = root.val;
//遍歷右節點,如果無序返回false
if (!isValidBST(root.right)) return false;
return true;
}
}
108.將有序數組轉換爲二叉搜索樹
二分查找時的查找狀態樹其實就是一個二分搜索樹,所以模擬二分查找就可以構建一棵BST
public class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
return toBST(nums, 0, nums.length - 1);
}
private TreeNode toBST(int[] nums, int sIndex, int eIndex) {
if (sIndex > eIndex) return null;
int mIndex = (sIndex + eIndex) << 1;//防止噁心的邊界值越界
TreeNode root = new TreeNode(nums[mIndex]);
root.left = toBST(nums, sIndex, mIndex - 1);
root.right = toBST(nums, mIndex + 1, eIndex);
return root;
}
}
109.有序鏈表轉換二叉搜索樹
和上一題將有序數組轉換爲二叉搜索樹思路一致,通過二分排序的狀態樹構建BFS
沒有了下標計算獲取中點,可以通過快慢指針來尋找鏈表的中點
public class Solution {
public TreeNode sortedListToBST(ListNode head) {
if (head == null) return null;
if (head.next == null) return new TreeNode(head.val);
ListNode preMid = preMid(head);//獲取中點的前一個節點
ListNode mid = preMid.next;//獲取中點
preMid.next = null;//斷開兩個鏈表
TreeNode root = new TreeNode(mid.val);
root.left = sortedListToBST(head);
root.right = sortedListToBST(mid.next);
return root;
}
//快慢指針尋找鏈表中點前一個節點,爲了完成斷開鏈表
private ListNode preMid(ListNode head) {
ListNode fast = head.next;
ListNode slow = head;
ListNode pre = head;
while (fast != null && fast.next != null) {
pre = slow;
slow = slow.next;
fast = fast.next.next;
}
return pre;
}
}
653.兩數之和IV-輸入BST
利用BST中序遍歷有序的特性,先採用中序遍歷,然後採用雙指針查詢
public class Solution {
List<Integer> nums;
public boolean findTarget(TreeNode root, int k) {
nums = new ArrayList<>();
//中序遍歷
search(root);
//雙指針查找
int left = 0;
int right = nums.size() - 1;
while (left < right) {
int compare = nums.get(left) + nums.get(right);
if (compare == k) return true;
else if (compare > k) right--;
else left++;
}
return false;
}
//中序遍歷
private void search(TreeNode root) {
if (root == null) return;
search(root.left);
nums.add(root.val);
search(root.right);
}
}
530.二叉搜索樹的最小絕對差
利用BST中序遍歷是升序的特點,逐個比較差值,記錄最小的差值返回
public class Solution {
int min = Integer.MAX_VALUE;//最小差值
int pre = -1;//上一個節點的值
public int getMinimumDifference(TreeNode root) {
search(root);
return min;
}
//中序遍歷
private void search(TreeNode root) {
if (root == null) return;
search(root.left);
if (pre != -1) {//如果訪問的不是第一個節點
min = Math.min(min, root.val - pre);
}
pre = root.val;
search(root.right);
}
}
501.二叉搜索樹中的衆數
充分利用BST中序遍歷是升序的,具體邏輯見代碼
public class Solution {
List<Integer> nums;//衆數集合
int curcount = 0;//當前計數
int maxcount = 0;//最大個數
Integer pre = null;//前一個節點的值,爲了判斷第一個節點,用包裝類初始化爲null
public int[] findMode(TreeNode root) {
nums = new ArrayList<>();
search(root);
//複製list值到數組
int res[] = new int[nums.size()];
for (int i = 0; i < nums.size(); i++) {
res[i] = nums.get(i);
}
return res;
}
private void search(TreeNode root) {
if (root == null) return;
search(root.left);
//不是第一個節點且與前一個值相同,計數加1
if (pre != null && pre == root.val) curcount++;
//第一個節點或者與前一個值不同,計數更新爲1
else curcount = 1;
//如果當前計數和最大計數相同,將當前值加入衆數集合
if (curcount == maxcount) nums.add(root.val);
//如果當前計數大於最大計數,清空衆數集合,加入當前值,更新最佳計數
else if (curcount > maxcount) {
nums.clear();
nums.add(root.val);
maxcount = curcount;
}
//暫存值
pre = root.val;
search(root.right);
}
}