二叉树问题
1 模板
1.1 递归(先、中、后)
数据结构:
public class Node {
int val;
Node left;
Node right;
public Node(int val){
this.val = val;
}
}
模板:
public void preOrder(Node head) {
if (head == null) {
return;
}
System.out.println(head.val + " ");
preOrder(head.left);
preOrder(head.right);
}
public void inOrder(Node head) {
if (head == null) {
return;
}
inOrder(head.left);
System.out.println(head.val + " ");
inOrder(head.right);
}
public void posOrder(Node head) {
if (head == null) {
return;
}
posOrder(head.left);
posOrder(head.right);
System.out.println(head.val + " ");
}
1.2 非递归
1.2.1 先序
思想:
访问完父节点需要访问左节点和右节点,因此利用栈来记录左节点和右节点(注意出入栈顺序)。
模板:
public static void preOrder(Node head) {
System.out.print("Pre-order: ");
if (head == null) {
return;
}
Stack<Node> stack = new Stack<>();
stack.push(head);
while (!stack.isEmpty()) {
head = stack.pop();
System.out.print(head.val + " ");
if (head.right != null) {
stack.push(head.right);
}
if (head.left != null) {
stack.push(head.left);
}
}
System.out.println();
}
1.2.2 中序
思想:
访问完左节点需要通过父节点访问剩余节点,因此利用栈来记录父节点。
模板:
public static void inOrder1(Node head) {
System.out.print("In-order: ");
if (head == null) {
return;
}
Stack<Node> stack = new Stack<>();
while (head != null || !stack.isEmpty()) {
if (head != null) {
stack.push(head);
head = head.left;
} else {
head = stack.pop();
System.out.print(head.val + " ");
head = head.right;
}
}
System.out.println();
}
public static void inOrder2(Node head) {
System.out.print("In-order: ");
if (head == null) {
return;
}
Stack<Node> stack = new Stack<>();
while (head != null || !stack.isEmpty()) {
while (head != null) {
stack.push(head);
head = head.left;
}
head = stack.pop();
System.out.print(head.val + " ");
head = head.right;
}
System.out.println();
}
1.2.3 后序
思想:
模板:
public static void posOrder1(Node head) {
System.out.print("Pos-order: ");
if (head == null){
return;
}
Stack<Node> stack1 = new Stack<>();
Stack<Node> stack2 = new Stack<>();
stack1.push(head);
while (!stack1.isEmpty()){
head = stack1.pop();
stack2.push(head);
if (head.left != null){
stack1.push(head.left);
}
if (head.right!=null){
stack1.push(head.right);
}
}
while (!stack2.isEmpty()){
System.out.print(stack2.pop().val + " ");
}
System.out.println();
}
public static void posOrder2(Node head) {
System.out.print("Pos-order: ");
if (head == null){
return;
}
Stack<Node> stack = new Stack<>();
Node popNode = head;
Node peekNode = head;
stack.push(head);
while (!stack.isEmpty()){
peekNode = stack.peek();
if (peekNode.left != null && peekNode.left != popNode && peekNode.right != popNode){
// 左孩子
stack.push(peekNode.left);
}else if (peekNode.right != null && peekNode.right != popNode){
// 右孩子
stack.push(peekNode.right);
}else {
System.out.print(stack.pop().val + " ");
popNode = peekNode;
}
}
System.out.println();
}
1.3 层序遍历
不记录层次
public static void levelOrder(Node head) {
System.out.print("Level-order: ");
if (head == null) {
return;
}
Queue<Node> queue = new LinkedList<>();
queue.add(head);
while (!queue.isEmpty()) {
head = queue.poll();
System.out.print(head.val + " ");
if (head.left != null) {
queue.add(head.left);
}
if (head.right != null) {
queue.add(head.right);
}
}
System.out.println();
}
记录层次
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> lists = new LinkedList<>();
if (root == null){
return lists;
}
TreeNode last = root;
TreeNode nLast = null;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
List<Integer> list = new LinkedList<>();
while (!queue.isEmpty()){
root = queue.poll();
list.add(root.val);
if (root.left != null){
queue.offer(root.left);
nLast = root.left;
}
if (root.right != null){
queue.offer(root.right);
nLast = root.right;
}
if (root == last){
lists.add(list);
list = new LinkedList<>();
last = nLast;
}
}
return lists;
}
1.4 Moris遍历
思想:
Moris遍历利用二叉树中的空指针实现遍历,空间复杂度O(1),时间复杂度O(N),提高时间复杂度的常数。
2 例题
2.1 LeetCode 98. 验证二叉搜索树
题目描述:
给定一个二叉树,判断其是否是一个有效的二叉搜索树。
假设一个二叉搜索树具有如下特征:
- 节点的左子树只包含小于当前节点的数。
- 节点的右子树只包含大于当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
实现思路:
中序遍历(可用非递归),数列递增。
参考代码:
public boolean isValidBST(TreeNode root) {
if(root == null){
return true;
}
Stack<TreeNode> stack = new Stack();
TreeNode pre = null;
while(root != null || !stack.isEmpty()){
while(root != null){
stack.push(root);
root = root.left;
}
root = stack.pop();
if(pre != null && root.val <= pre.val){
return false;
}
pre = root;
root = root.right;
}
return true;
}
2.2 LeetCode 101. 对称二叉树
题目描述:
给定一个二叉树,检查它是否是镜像对称的。
实现思路:
- 递归,双指针遍历。
- 非递归,翻译递归。
参考代码:
递归,双指针遍历
public boolean isSymmetric(TreeNode root) {
return check(root,root);
}
public boolean check(TreeNode p1,TreeNode p2){
if(p1 == null && p2 == null){
return true;
}
if(p1 == null || p2 == null){
return false;
}
return p1.val == p2.val && check(p1.left, p2.right) && check(p1.right, p2.left);
}
非递归
public boolean isSymmetric(TreeNode root) {
if(root == null){
return true;
}
Stack<TreeNode> stack1 = new Stack();
Stack<TreeNode> stack2 = new Stack();
TreeNode p1 = root;
TreeNode p2 = root;
stack1.push(p1);
stack2.push(p2);
while(!stack1.isEmpty() && !stack2.isEmpty()){
p1 = stack1.pop();
p2 = stack2.pop();
if(p1.val != p2.val){
return false;
}
if((p1.left != null && p2.right == null) || (p1.left == null && p2.right != null)){
return false;
}
if((p1.right != null && p2.left == null) || (p1.right == null && p2.left != null)){
return false;
}
if(p1.left != null){
stack1.push(p1.left);
}
if(p1.right != null){
stack1.push(p1.right);
}
if(p2.right != null){
stack2.push(p2.right);
}
if(p2.left != null){
stack2.push(p2.left);
}
}
return true;
}
2.3 LeetCode 108. 将有序数组转换为二叉搜索树
题目描述:
将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。
本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
实现思路:
中序遍历,且根节点每次选择中间位置。
参考代码:
public TreeNode sortedArrayToBST(int[] nums) {
if(nums == null || nums.length == 0){
return null;
}
return process(nums, 0, nums.length - 1);
}
public TreeNode process(int[] nums, int start, int end){
int mid = start + (end - start) / 2;
TreeNode root = new TreeNode(nums[mid]);
if(start < mid){
root.left = process(nums, start, mid - 1);
}
if(mid < end){
root.right = process(nums, mid + 1, end);
}
return root;
}
2.4 LeetCode 103. 二叉树的锯齿形层次遍历
题目描述:
给定一个二叉树,返回其节点值的锯齿形层次遍历。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。
实现思路:
层序遍历,用一个双端队列记录。
参考代码:
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
List<List<Integer>> lists = new LinkedList();
if(root == null){
return lists;
}
boolean lr = true;
TreeNode last = root;
TreeNode nLast = null;
Queue<TreeNode> queue = new LinkedList();
Deque<Integer> deque = new LinkedList();
queue.add(root);
while(!queue.isEmpty()){
root = queue.poll();
deque.addLast(root.val);
if(root.left != null){
queue.add(root.left);
nLast = root.left;
}
if(root.right != null){
queue.add(root.right);
nLast = root.right;
}
if(root == last){
addList(deque, lists, lr);
lr = !lr;
last = nLast;
}
}
return lists;
}
public void addList(Deque<Integer> deque, List<List<Integer>> lists, boolean lr){
List<Integer> list = new LinkedList();
if(lr){
while(!deque.isEmpty()){
list.add(deque.pollFirst());
}
}else{
while(!deque.isEmpty()){
list.add(deque.pollLast());
}
}
lists.add(list);
}
2.5 LeetCode 105. 从前序与中序遍历序列构造二叉树
题目描述:
根据一棵树的前序遍历与中序遍历构造二叉树。
注意:
你可以假设树中没有重复的元素。
实现思路:
递归:根据先序第一个节点,拆分中序数组,然后根据偏移量拆分先序数组。
先序 [ 根节点, [左子树的前序遍历结果], [右子树的前序遍历结果] ]
中序 [ [左子树的中序遍历结果], 根节点, [右子树的中序遍历结果] ]
参考代码:
public TreeNode buildTree(int[] preorder, int[] inorder) {
if(preorder == null || inorder == null || preorder.length == 0 || inorder.length == 0 || preorder.length != inorder.length){
return null;
}
HashMap<Integer,Integer> map = new HashMap();
for(int i = 0 ; i < inorder.length ; i++){
map.put(inorder[i],i);
}
return process(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1, map);
}
public TreeNode process(int[] preorder, int s1, int e1, int[] inorder, int s2, int e2, HashMap<Integer,Integer> map){
if(s1 > e1){
return null;
}
TreeNode root = new TreeNode(preorder[s1]);
int index = map.get(preorder[s1]);
root.left = process(preorder, s1 + 1, s1 + (index - s2), inorder, s2, index - 1, map);
root.right = process(preorder, s1 + (index - s2) + 1, e1, inorder, index + 1, e2, map);
return root;
}
2.6 LeetCode 230. 二叉搜索树中第K小的元素
题目描述:
给定一个二叉搜索树,编写一个函数 kthSmallest 来查找其中第 k 个最小的元素。
说明:
你可以假设 k 总是有效的,1 ≤ k ≤ 二叉搜索树元素个数。
实现思路:
中序
参考代码:
public int kthSmallest(TreeNode root, int k) {
if(root == null || k < 0){
return -1;
}
Stack<TreeNode> stack = new Stack();
while(root != null || !stack.isEmpty()){
while(root != null){
stack.add(root);
root = root.left;
}
root = stack.pop();
if(--k == 0){
return root.val;
}
root = root.right;
}
return -1;
}
2.7 LeetCode 236. 二叉树的最近公共祖先
题目描述:
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
实现思路:
后序
参考代码:
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null || root == p || root == q){
return root;
}
TreeNode left = lowestCommonAncestor(root.left,p,q);
TreeNode right = lowestCommonAncestor(root.right,p,q);
if(left != null && right != null){
return root;
}
return left != null ? left : right;
}
2.8 LeetCode 124. 二叉树中的最大路径和
题目描述:
给定一个非空二叉树,返回其最大路径和。
本题中,路径被定义为一条从树中任意节点出发,达到任意节点的序列。该路径至少包含一个节点,且不一定经过根节点。
实现思路:
后序遍历,根据左右字数返回的值(贡献值)计算当前树的最大路径值。
参考代码:
public int max = Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) {
process(root);
return max;
}
public int process(TreeNode root){
if(root == null){
return 0;
}
int leftMax = Math.max(0, process(root.left));
int rightMax = Math.max(0, process(root.right));
max = Math.max(max, root.val + leftMax + rightMax);
return root.val + Math.max(leftMax, rightMax);
}