二叉树的遍历
1
/ \
2 3
/ \ \
4 5 6
- 层次遍历顺序:[1 2 3 4 5 6]
- 前序遍历顺序:[1 2 4 5 3 6] 根左右
- 中序遍历顺序:[4 2 5 1 3 6] 左根右
- 后序遍历顺序:[4 5 2 6 3 1] 左右根
层次遍历使⽤ BFS 实现,利⽤的就是 BFS ⼀层⼀层遍历的特性;⽽前序、中序、后序遍历利⽤了 DFS 实现。前序、中序、后序遍只是在对节点访问的顺序有⼀点不同,其它都相同。
递归实现
前序
void dfs(TreeNode root) {
visit(root);
dfs(root.left);
dfs(root.right);
}
中序
void dfs(TreeNode root){
dfs(root.left);
visit(root);
dfs(root.right);
}
后序
void dfs(TreeNode root){
dfs(root.left);
dfs(root.right);
visit(root);
}
迭代实现
前序
class Solution {
//借助栈,前:根左右
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> ans = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while(!stack.isEmpty()){
TreeNode node = stack.pop();
//栈是先进后出,前序先访问左边后访问右边,所以先放右边,这样才能保证左子树先访问到
if(node == null) continue;
ans.add(node.val);
stack.push(node.right);
stack.push(node.left);
}
return ans;
}
}
中序
对于平衡二叉树来说,中序遍历得到的结果就是排序好的
class Solution {
//中序遍历,左根右
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> ans = new ArrayList<>();
if(root == null) return ans;
Stack<TreeNode> stack = new Stack<>();
//一直拿到最左边的节点
TreeNode cur = root;
while(cur != null || !stack.isEmpty()){
while(cur != null){
stack.push(cur);
cur = cur.left;
}
//直到拿到最左边的节点
TreeNode node = stack.pop();
ans.add(node.val);
//在组找当前节点的又节点
cur = node.right;
}
return ans;
}
}
后序
前序遍历为 root -> left -> right,后序遍历为 left -> right -> root。可以修改前序遍历成为 root -> right -> left,那么这个顺序就和后序遍历正好相反。
class Solution {
//参照前序遍历的 根左右,改成:根右左,最后翻转:左根右就是后续遍历
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> ans = new ArrayList<>();
if(root == null) return ans;
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while(!stack.isEmpty()){
TreeNode node = stack.pop();
if(node == null) continue;
//根右左
ans.add(node.val);
stack.push(node.left);
stack.push(node.right);
}
Collections.reverse(ans);
return ans;
}
}
101. 对称二叉树
递归版本:
class Solution {
public boolean isSymmetric(TreeNode root) {
return isMetirc(root.left,root.right);
}
public boolean isMetirc(TreeNode a,TreeNode b){
if(a == null && b == null) return true;
if(a == null || b == null) return false;
if(a.val != b.val) return false;
return isMetirc(a.left, b.right) && isMetirc(a.right, b.left);
}
}
迭代版本:
树的层序遍历,都是借用队列来实现。
class Solution {
public boolean isSymmetric(TreeNode root) {
return check(root, root);
}
public boolean check(TreeNode u, TreeNode v) {
Queue<TreeNode> q = new LinkedList<TreeNode>();
q.offer(u);
q.offer(v);
while (!q.isEmpty()) {
u = q.poll();
v = q.poll();
if (u == null && v == null) {
continue;
}
if ((u == null || v == null) || (u.val != v.val)) {
return false;
}
q.offer(u.left);
q.offer(v.right);
q.offer(u.right);
q.offer(v.left);
}
return true;
}
}
104. 二叉树的最大深度
class Solution {
public int maxDepth(TreeNode root) {
if(root == null) return 0;
return Math.max(maxDepth(root.left),maxDepth(root.right)) + 1;
}
}
108. 将有序数组转换为二叉搜索树
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
return toBST(nums,0,nums.length - 1);
}
public TreeNode toBST(int[] nums,int left,int right){
if(left > right) return null;
int mid = (left + right) / 2;
TreeNode root = new TreeNode(nums[mid]);
root.left = toBST(nums,left,mid - 1);
root.right = toBST(nums,mid + 1,right);
return root;
}
}
109. 有序链表转换二叉搜索树
与有序数组转换为二叉搜索树相同:
难点:
- 通过快慢指针找到链表的中点的前一个节点。
- 找到中点后断链,将前部分,mid,剩下的部分分别作为二叉树的左子树和右子树
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);
//断连,mid 为根节点
ListNode mid = preMid.next;
preMid.next = null;
TreeNode root = new TreeNode(mid.val);
root.left = sortedListToBST(head);
root.right = sortedListToBST(mid.next);
return root;
}
//找到中点的前一个节点
public ListNode preMid(ListNode head){
if(head == null) return head;
ListNode slow = head,fast = head.next,pre = head;
while(fast != null && fast.next != null){
pre = slow;
slow = slow.next;
fast = fast.next.next;
}
return pre;
}
}
110. 平衡二叉树
代码:
class Solution {
boolean result = true;
public boolean isBalanced(TreeNode root) {
dep(root);
return result;
}
public int dep(TreeNode node){
if(node == null) return 0;
int left = dep(node.left);
int right = dep(node.right);
if(Math.abs(left - right) > 1) result = false;
return Math.max(left,right) + 1;
}
}
111. 二叉树的最小深度
class Solution {
public int minDepth(TreeNode root) {
if(root == null) return 0;
int left = minDepth(root.left);
int right = minDepth(root.right);
if(left == 0 || right == 0) return left + right + 1;
return Math.min(left,right) + 1;
}
}
112. 路径总和
class Solution {
public boolean hasPathSum(TreeNode root, int targetSum) {
if(root == null) return false;
if(root.left == null && root.right == null && root.val == targetSum) return true;
return hasPathSum(root.left,targetSum - root.val) || hasPathSum(root.right,targetSum - root.val);
}
}
144. 二叉树的前序遍历
前序遍历:根左右
1.递归
class Solution {
//递归方法,前:根左右
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> ans = new ArrayList<>();
dfs(root,ans);
return ans;
}
public void dfs(TreeNode root,List<Integer> ans){
if(root == null) return;
ans.add(root.val);
dfs(root.left,ans);
dfs(root.right,ans);
}
}
226. 翻转二叉树
class Solution {
//就跟swap函数交换一样
public TreeNode invertTree(TreeNode root) {
if(root == null) return null;
TreeNode left = root.left;
root.left = invertTree(root.right);
root.right = invertTree(left);
return root;
}
}
230. 二叉搜索树中第K小的元素
二叉搜索树的中序遍历为有序数组。
class Solution {
private int cnt = 0;
private int ans = 0;
//中序遍历
public int kthSmallest(TreeNode root, int k) {
dfs(root,k);
return ans;
}
public void dfs(TreeNode root,int k){
if(root == null) return;
dfs(root.left,k);
cnt++;
if(cnt == k){
ans = root.val;
return;
}
dfs(root.right,k);
}
}
337. 打家劫舍 III
方法一:递归
分情况:偷 root 节点 或 不偷 root节点,leetCode 提交超时,然后采用HashMap来做备忘录,还是超时。
class Solution {
//递归,分两种情况:偷root节点,不偷root节点
public int rob(TreeNode root) {
if(root == null) return 0;
//偷root节点
int rootVal = root.val;
if(root.left != null) rootVal += rob(root.left.left) + rob(root.left.right);
if(root.right != null) rootVal += rob(root.right.left) + rob(root.right.right);
//不偷root节点
int noRoot = rob(root.left) + rob(root.right);
return Math.max(rootVal,noRoot);
}
}
方法二:递归借助 HashMap
class Solution {
//递归,分两种情况:偷root节点,不偷root节点,超时
public int rob(TreeNode root) {
Map<TreeNode,Integer> map = new HashMap<>();
return rob(root,map);
}
public int rob(TreeNode root,Map<TreeNode,Integer> map){
if(root == null) return 0;
//借助备忘录
if(map.containsKey(root)) return map.get(root);
//偷root节点
int rootVal = root.val;
if(root.left != null) rootVal += rob(root.left.left) + rob(root.left.right);
if(root.right != null) rootVal += rob(root.right.left) + rob(root.right.right);
//不偷root节点
int noRoot = rob(root.left) + rob(root.right);
int max = Math.max(rootVal,noRoot);
map.put(root,max);
return max;
}
}
方法三:
每个节点可选择偷或者不偷两种状态,根据题目意思,相连节点不能一起偷
1.当前节点选择偷时,那么两个孩子节点就不能选择偷了
2.当前节点选择不偷时,两个孩子节点只需要拿最多的钱出来就行(两个孩子节点偷不偷没关系)
3.我们使用一个大小为 2 的数组来表示 int[] res = new int[2] 0 代表不偷,1 代表偷
任何一个节点能偷到的最大钱的状态可以定义为
当前节点选择不偷:当前节点能偷到的最大钱数 = 左孩子能偷到的钱 + 右孩子能偷到的钱
当前节点选择偷:当前节点能偷到的最大钱数 = 左孩子选择自己不偷时能得到的钱 + 右孩子选择不偷时能得到的钱 + 当前节点的钱数
表示为公式如下
root[0] = Math.max(rob(root.left)[0], rob(root.left)[1]) + Math.max(rob(root.right)[0], rob(root.right)[1])
root[1] = rob(root.left)[0] + rob(root.right)[0] + root.val;
class Solution {
public int rob(TreeNode root) {
int[] ans = dfs(root);
return Math.max(ans[0],ans[1]);
}
// int[] = new int[2] 数组中 0 表示该点不偷,1 表示该点偷
public int[] dfs(TreeNode root){
if(root == null) return new int[2];
int[] result = new int[2];
int[] left = dfs(root.left);
int[] right = dfs(root.right);
//情况1,不偷 root节点,则可以偷 left 节点,改节点中取较大值,即可偷left,也可以不偷left,选择其中较大的值
result[0] = Math.max(left[0],left[1]) + Math.max(right[0],right[1]);
//情况2,偷了 root 节点,则left和right都不能偷
result[1] = left[0] + right[0] + root.val;
return result;
}
}
404. 左叶子之和
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);
}
//判定该节点是否是叶子节点
public boolean isLeaf(TreeNode node){
if(node == null) return false;
return node.left == null && node.right == null;
}
}
437. 路径总和 III
因为题目要求是从任何节点往下遍历都可以,所以分两种情况:
- 包括当前 root 节点的递归
- 不包括当前节点的,只从左右子树中查找
class Solution {
//分三种情况递归包括root节点,不包括root节点
public int pathSum(TreeNode root, int targetSum) {
if(root == null) return 0;
return pathWithRootSum(root,targetSum) + pathSum(root.left,targetSum) + pathSum(root.right,targetSum);
}
public int pathWithRootSum(TreeNode root,int targetSum){
if(root == null) return 0;
int ret = 0;
targetSum -= root.val;
if(targetSum == 0) ret++;
//查找左右子树
ret += pathWithRootSum(root.left,targetSum) + pathWithRootSum(root.right,targetSum);
return ret;
}
}
513. 找树左下角的值
树的层次遍历,广度遍历
思路:需要找到最底层的最左边的节点,那就通过Queue来实现层次遍历。
注意我们需要找到的是当前层的最左边的节点,基于队列的先进先出的思想。需要先放右节点,在放左节点,这样才能找到最后一个左叶子结点。
class Solution {
/**
* 深度优先搜索,也是借助队列,一直找到最底层坐标的节点
* 前:根左右
* 中:左根右
* 后:左右跟
*/
public int findBottomLeftValue(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while(!queue.isEmpty()){
root = queue.poll();
//先添加右边节点,在添加左的节点,队列中先添加的先移出
if(root.right != null) queue.add(root.right);
if(root.left != null) queue.add(root.left);
}
return root.val;
}
}
538. 把二叉搜索树转换为累加树
class Solution {
int sum = 0;
//中序遍历有序,递增(左根右)--》那现在反向遍历:右根左
public TreeNode convertBST(TreeNode root) {
dfs(root);
return root;
}
public void dfs(TreeNode root){
if(root == null) return;
dfs(root.right);
sum += root.val;
root.val = sum;
dfs(root.left);
}
}
543. 二叉树的直径
class Solution {
private int max;
//注意是路径总和,不是节点总和
public int diameterOfBinaryTree(TreeNode root) {
depth(root);
return max;
}
public int depth(TreeNode root){
if(root == null) return 0;
int left = depth(root.left);
int right = depth(root.right);
max = Math.max(max,left + right);
//选择较长的路劲返回
return Math.max(left,right) + 1;
}
}
572. 另一棵树的子树
class Solution {
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
if(root == null) return false;
return isSame(root,subRoot) || isSubtree(root.left,subRoot) || isSubtree(root.right,subRoot);
}
public boolean isSame(TreeNode a,TreeNode b){
if(a == null && b == null) return true;
if(a == null || b == null) return false;
if(a.val != b.val) return false;
return isSame(a.left,b.left) && isSame(a.right,b.right);
}
}
617. 合并二叉树
方法一:以左子树为合并后的树
class Solution {
//方法一:直接以左边的树为合并后的树
public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
if(root1 == null) return root2;
if(root2 == null) return root1;
root1.val += root2.val;
root1.left = mergeTrees(root1.left, root2.left);
root1.right = mergeTrees(root1.right, root2.right);
return root1;
}
}
方法二:新建一个二叉树
class Solution {
//方法二:新建一个二叉树
public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
if(root1 == null) return root2;
if(root2 == null) return root1;
TreeNode root = new TreeNode(root1.val + root2.val) ;
root.left = mergeTrees(root1.left, root2.left);
root.right = mergeTrees(root1.right, root2.right);
return root;
}
}
637. 二叉树的层平均值
1.广度优先搜索
借助队列来实现二叉树的层次遍历,每一层中,当前queue的大小就是当前层的节点的数量。
class Solution {
//借助队列来实现层次遍历,当前队列的 size 就是这一层节点的数量
public List<Double> averageOfLevels(TreeNode root) {
List<Double> ans = new ArrayList<>();
if(root == null) return ans;
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while(!queue.isEmpty()){
int size = queue.size();
double sum = 0;
for(int i = 0; i < size; i++){
TreeNode node = queue.poll();
sum += node.val;
if(node.left != null) queue.add(node.left);
if(node.right != null) queue.add(node.right);
}
ans.add(sum / size);
}
return ans;
}
}
653. 两数之和 IV - 输入 BST
class Solution {
//先中序遍历,后双指针,或者借助HashSet
public boolean findTarget(TreeNode root, int k) {
List<Integer> num = new ArrayList<>();
dfs(root,num);
int i = 0,j = num.size() - 1;
while(i < j){
int sum = num.get(i) + num.get(j);
if(sum == k) return true;
if(sum < k) i++;
else j--;
}
return false;
}
public void dfs(TreeNode root,List<Integer> list){
if(root == null) return;
dfs(root.left,list);
list.add(root.val);
dfs(root.right,list);
}
}
669. 修剪二叉搜索树
class Solution {
//二叉搜索树的左子树比跟节点小,右子树比根节点大
public TreeNode trimBST(TreeNode root, int low, int high) {
if(root == null) return null;
//情况1:low还小,则只能往右子树找了
if(root.val < low) return trimBST(root.right,low,high);
//情况2:hight还大,则只能往左子树裁找了
if(root.val > high) return trimBST(root.left,low,high);
//情况3:在区间内,直接递归
root.left = trimBST(root.left,low,high);
root.right = trimBST(root.right,low,high);
return root;
}
}
671. 二叉树中第二小的节点
方法一:递归
class Solution {
public int findSecondMinimumValue(TreeNode root) {
if(root == null) return -1;
if(root.left == null && root.right == null) return -1;
int leftValue = root.left.val;
int rightValue = root.right.val;
//当前的值相等,那我们的继续往下找,因为root节点的值是等于子节点中的某个节点的值
if(root.val == leftValue) leftValue = findSecondMinimumValue(root.left);
if(root.val == rightValue) rightValue = findSecondMinimumValue(root.right);
//如果找到了,则表明结果不为 - 1
if(leftValue != -1 && rightValue != -1) return Math.min(leftValue,rightValue);
if(leftValue != -1) return leftValue;
return rightValue;
}
}
方法二:
我们可以使用深度优先搜索的方法对二叉树进行遍历。
假设当前遍历到的节点为 node,如果 node 的值严格大于 rootvalue,那么我们就可以用 node 的值来更新答案ans。
当我们遍历完整棵二叉树后,即可返回ans。
根据题目要求,如果第二小的值不存在的话,输出 −1,那么我们可以将 ans 的初始值置为 −1。在遍历的过程中,如果当前节点的值严格大于 rootvalue 的节点时,那么只要ans 的值为 −1 或者当前节点的值严格小于ans,我们就需要对 ans 进行更新。
此外,如果当前节点的值大于等于ans,那么根据「思路」部分,以当前节点为根的子树中所有节点的值都大于等于 ans,我们就直接回溯,无需对该子树进行遍历。这样做可以省去不必要的遍历过程。
class Solution {
private int ans = -1;//记录结果
private int rootValue;
public int findSecondMinimumValue(TreeNode root) {
if(root == null) return -1;
rootValue = root.val;
dfs(root);
return ans;
}
public void dfs(TreeNode root){
if(root == null) return;
//找到严格意义上大于 root节点的值
if(ans != -1 && root.val >= ans) return;
if(root.val > rootValue){
ans = root.val;
}
dfs(root.left);
dfs(root.right);
}
}
687. 最长同值路径
class Solution {
private int maxLength = 0;
public int longestUnivaluePath(TreeNode root) {
dfs(root);
return maxLength;
}
public int dfs(TreeNode head){
if(head == null) return 0;
int left = dfs(head.left);
int right = dfs(head.right);
//与根节点的值相同
int leftPath = head.left != null && head.left.val == head.val ? left + 1: 0;
int rightPath = head.right != null && head.right.val == head.val ? right + 1: 0;
maxLength = Math.max(maxLength,leftPath + rightPath);
return Math.max(leftPath,rightPath);
}
}