以下算法题均为力扣原题,对自己刷过的一些二叉树相关算法题做一个总结,便于日后复习。
定义一棵二叉树:
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
根据一棵树的前序遍历与中序遍历构造二叉树。
注意:
你可以假设树中没有重复的元素。
例如,给出
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
3
/ \
9 20
/ \
15 7
代码实现:
public TreeNode buildTree(int[] preorder, int[] inorder) {
if (preorder == null || preorder.length == 0 || inorder == null || inorder.length == 0 || preorder.length != inorder.length) {
return null;
}
return help(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1);
}
private TreeNode help(int[] preorder, int pStart, int pEnd, int[] inorder, int iStart, int iEnd) {
//递归的第一步:递归终止条件,避免死循环
if (pStart > pEnd || iStart > iEnd) {
return null;
}
//重建根节点
TreeNode treeNode = new TreeNode(preorder[pStart]);
int index = 0; //index找到根节点在中序遍历的位置
while (inorder[iStart + index] != preorder[pStart]) {
index++;
}
//重建左子树
treeNode.left = help(preorder, pStart + 1, pStart + index, inorder, iStart, iStart + index - 1);
//重建右子树
treeNode.right = help(preorder, pStart + index + 1, pEnd, inorder, iStart + index + 1, iEnd);
return treeNode;
}
根据一棵树的中序遍历与后序遍历构造二叉树。
注意:
你可以假设树中没有重复的元素。
例如,给出
中序遍历 inorder = [9,3,15,20,7]
后序遍历 postorder = [9,15,7,20,3]
返回如下的二叉树:
3
/ \
9 20
/ \
15 7
代码实现:
public class Solution {
public TreeNode buildTree(int[] inorder, int[] postorder) {
int inLen = inorder.length;
int postLen = postorder.length;
// 特判
if (inLen != postLen) {
throw new RuntimeException("输入错误");
}
return buildTree(inorder, 0, inLen - 1, postorder, 0, postLen - 1);
}
/**
* 使用中序遍历序列 inorder 的子区间 [inLeft, inRight]
* 与后序遍历序列 postorder 的子区间 [postLeft, postRight] 构建二叉树
*
* @param inorder 中序遍历序列
* @param inLeft 中序遍历序列的左边界
* @param inRight 中序遍历序列的右边界
* @param postorder 后序遍历序列
* @param postLeft 后序遍历序列的左边界
* @param postRight 后序遍历序列的右边界
* @return 二叉树的根结点
*/
private TreeNode buildTree(int[] inorder, int inLeft, int inRight,
int[] postorder, int postLeft, int postRight) {
if (inLeft > inRight || postLeft > postRight) {
return null;
}
int pivot = postorder[postRight];
int pivotIndex = inLeft;
// 注意这里如果编写不当,有数组下标越界的风险
while (inorder[pivotIndex] != pivot) {
pivotIndex++;
}
TreeNode root = new TreeNode(pivot);
root.left = buildTree(inorder, inLeft, pivotIndex - 1,
postorder, postLeft, postRight - inRight + pivotIndex - 1);
root.right = buildTree(inorder, pivotIndex + 1, inRight,
postorder, postRight - inRight + pivotIndex, postRight - 1);
return root;
}
}
返回与给定的前序和后序遍历匹配的任何二叉树。
pre 和 post 遍历中的值是不同的正整数。
示例:
输入:pre = [1,2,4,5,3,6,7], post = [4,5,2,6,7,3,1]
输出:[1,2,3,4,5,6,7]
思路:
我们知道,在前序遍历的时候,根节点是第一个输出,而在后序遍历中,根节点是最后一个输出;
那么我们可以利用前序遍历来构建Tree,然后通过后续遍历来检验当前树是否构建完毕。
首先我们创建一个节点作为 root,root = TreeNode(pre[preIndex])当 root.val == post[posIndex]时, 意味着我们已经构建完毕了当前子树,如果当前子树没有构建完毕,那么我们就递归的构建左右子树。
代码实现:
class Solution {
int preIndex = 0, posIndex = 0;
public TreeNode constructFromPrePost(int[] pre, int[] post) {
TreeNode root = new TreeNode(pre[preIndex++]);
if (root.val != post[posIndex])
root.left = constructFromPrePost(pre, post);
if (root.val != post[posIndex])
root.right = constructFromPrePost(pre, post);
posIndex++;
return root;
}
}
我们从二叉树的根节点 root 开始进行深度优先搜索。
在遍历中的每个节点处,我们输出 D 条短划线(其中 D 是该节点的深度),然后输出该节点的值。(如果节点的深度为 D,则其直接子节点的深度为 D + 1。根节点的深度为 0)。
如果节点只有一个子节点,那么保证该子节点为左子节点。
给出遍历输出 S,还原树并返回其根节点 root。
示例 1:
输入:"1-2--3--4-5--6--7"
输出:[1,2,5,3,4,6,7]
示例 2:
输入:"1-2--3---4-5--6---7"
输出:[1,2,5,3,null,6,null,4,null,7]
示例 3:
输入:"1-401--349---90--88"
输出:[1,401,null,349,88,90]
代码实现:
1)递归解法:
class Solution {
int cur=0,curD=0;
public TreeNode recoverFromPreorder(String S) {
char[] nodes = S.toCharArray();
return dfs(0,nodes);
}
public TreeNode dfs(int depth, char[] nodes){
int val = 0;
for(;curdepth) r.left = dfs(curD,nodes);
if(curD>depth) r.right = dfs(curD,nodes);
return r;
}
}
//全局的,记录访问到字符串S的位置
int index = 0;
public TreeNode recoverFromPreorder(String S) {
return helper(S, 0);
}
public TreeNode helper(String s, int depth) {
int level = 0;
//获取数字在树的第几层
while (index + level < s.length() && s.charAt(index + level) == '-') {
level++;
}
//如果遍历的深度和获取到的深度不一致,返回空
if (level != depth)
return null;
int next = index + level;
//获取数字
while (next < s.length() && s.charAt(next) != '-')
next++;
int val = Integer.parseInt(s.substring(index + level, next));
index = next;
//创建结点
TreeNode root = new TreeNode(val);
root.left = helper(s, depth + 1);
if (root.left == null) {//如果左子节点是空,那么右子节点肯定也是空的
root.right = null;
} else {
root.right = helper(s, depth + 1);
}
return root;
}
2)非递归解法:
class Solution {
int cur=0,curD=0;
public TreeNode recoverFromPreorder(String S) {
if(S==null)
return null;
char[] ch = S.toCharArray();
return dfs(0,ch);
}
public TreeNode dfs(int depth,char[] ch){
int value = 0;
while(curdepth){
root.left = dfs(curD,ch);
}
if(curD>depth){
root.right = dfs(curD,ch);
}
return root;
}
}
二叉搜索树中的两个节点被错误地交换。
请在不改变其结构的情况下,恢复这棵树。
示例 1:
输入: [1,3,null,null,2]
1
/
3
\
2
输出: [3,1,null,null,2]
3
/
1
\
2
示例 2:
输入: [3,1,4,null,null,2]
3
/ \
1 4
/
2
输出: [2,1,4,null,null,3]
2
/ \
1 4
/
3
代码实现:
class Solution {
public void recoverTree(TreeNode root) {
List list = new ArrayList();
dfs(root,list);
TreeNode x = null;
TreeNode y = null;
for(int i=0;ilist.get(i+1).val){
y = list.get(i+1);
//x,y不一定是相邻节点
if(x==null){
x = list.get(i);
}
}
}
if(x!=null && y!=null){
int temp = x.val;
x.val = y.val;
y.val = temp;
}
}
public void dfs(TreeNode root,List list){
if(root==null) return;
dfs(root.left,list);
list.add(root);
dfs(root.right,list);
}
}
将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。
本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
示例:
给定有序数组: [-10,-3,0,5,9],
一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树:
0
/ \
-3 9
/ /
-10 5
代码实现:
class Solution {
int[] nums;
public TreeNode sortedArrayToBST(int[] nums) {
this.nums = nums;
return helper(0,nums.length-1);
}
public TreeNode helper(int left,int right){
if(left>right){
return null;
}
int p=(left+right)>>1;
TreeNode root = new TreeNode(nums[p]);
root.left = helper(left,p-1);
root.right = helper(p+1,right);
return root;
}
}
实现一个函数,检查一棵二叉树是否为二叉搜索树。
示例 1:
输入:
2
/ \
1 3
输出: true
示例 2:
输入:
5
/ \
1 4
/ \
3 6
输出: false
解释: 输入为: [5,1,4,null,null,3,6]。根节点的值为 5 ,但是其右子节点值为 4 。
代码实现:
class Solution {
public boolean isValidBST(TreeNode root) {
if(root==null)
return true;
TreeNode maxLeft=root.left,minRight=root.right;
while(maxLeft!=null && maxLeft.right!=null)
maxLeft = maxLeft.right;
while(minRight!=null && minRight.left!=null)
minRight = minRight.left;
boolean flag = (maxLeft==null || maxLeft.valroot.val);
return flag && isValidBST(root.left) && isValidBST(root.right);
}
}
给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。
示例 1:
给定二叉树 [3,9,20,null,null,15,7]
3
/ \
9 20
/ \
15 7
返回 true 。
示例 2:
给定二叉树 [1,2,2,3,3,null,null,4,4]
1
/ \
2 2
/ \
3 3
/ \
4 4
返回 false 。
代码实现:
class Solution {
public boolean isBalanced(TreeNode root) {
if(root==null){
return true;
}
return Math.abs(height(root.left)-height(root.right))<2 && isBalanced(root.left) && isBalanced(root.right);
}
public int height(TreeNode root){//递归求结点的最大深度
if(root == null){
return 0;
}
return Math.max(height(root.left),height(root.right))+1;
}
}
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回它的最小深度 2.
代码实现:
class Solution {
public int minDepth(TreeNode root) {
if(root==null){
return 0;
}
if(root.left==null && root.right==null){
return 1;
}
int count = Integer.MAX_VALUE;
if(root.left!=null){
count = Math.min(count,minDepth(root.left));
}
if(root.right!=null){
count = Math.min(count,minDepth(root.right));
}
return count+1;
}
}
给定一个非空二叉树,返回其最大路径和。
本题中,路径被定义为一条从树中任意节点出发,达到任意节点的序列。该路径至少包含一个节点,且不一定经过根节点。
示例 1:
输入: [1,2,3]
1
/ \
2 3
输出: 6
示例 2:
输入: [-10,9,20,null,null,15,7]
-10
/ \
9 20
/ \
15 7
输出: 42
代码实现:
class Solution {
int maxSum = Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) {
maxGain(root);
return maxSum;
}
public int maxGain(TreeNode node) {
if (node == null) {
return 0;
}
// 递归计算左右子节点的最大贡献值
// 只有在最大贡献值大于 0 时,才会选取对应子节点
int leftGain = Math.max(maxGain(node.left), 0);
int rightGain = Math.max(maxGain(node.right), 0);
// 节点的最大路径和取决于该节点的值与该节点的左右子节点的最大贡献值
int priceNewpath = node.val + leftGain + rightGain;
// 更新答案
maxSum = Math.max(maxSum, priceNewpath);
// 返回节点的最大贡献值
return node.val + Math.max(leftGain, rightGain);
}
}
给定一个二叉树,原地将它展开为一个单链表。
例如,给定二叉树
1
/ \
2 5
/ \ \
3 4 6
将其展开为:
1
\
2
\
3
\
4
\
5
\
6
代码实现:
class Solution {
public void flatten(TreeNode root) {
if (root == null){
return;
}
Stack s = new Stack();
s.push(root);
TreeNode pre = null;
while (!s.isEmpty()) {
TreeNode temp = s.pop();
/***********修改的地方*************/
if(pre!=null){
pre.right = temp;
pre.left = null;
}
/********************************/
if (temp.right != null){
s.push(temp.right);
}
if (temp.left != null){
s.push(temp.left);
}
/***********修改的地方*************/
pre = temp;
/********************************/
}
}
}
给定一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树。
本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
示例:
给定的有序链表: [-10, -3, 0, 5, 9],
一个可能的答案是:[0, -3, 9, -10, null, 5], 它可以表示下面这个高度平衡二叉搜索树:
0
/ \
-3 9
/ /
-10 5
代码实现:
//递归实现
class Solution {
public TreeNode sortedListToBST(ListNode head) {
if(head==null) return null;
List list = new ArrayList();
while(head!=null){
list.add(head.val);
head = head.next;
}
return helper(list,0,list.size()-1);
}
public TreeNode helper(List list,int left,int right){
if(left>right) return null;
int mid = left+(right-left)/2;
TreeNode root = new TreeNode(list.get(mid));
root.left = helper(list,left,mid-1);
root.right = helper(list,mid+1,right);
return root;
}
}
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。
为了让您更好地理解问题,以下面的二叉搜索树为例:
我们希望将这个二叉搜索树转化为双向循环链表。链表中的每个节点都有一个前驱和后继指针。对于双向循环链表,第一个节点的前驱是最后一个节点,最后一个节点的后继是第一个节点。
下图展示了上面的二叉搜索树转化成的链表。“head” 表示指向链表中有最小元素的节点。
特别地,我们希望可以就地完成转换操作。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继。还需要返回链表中的第一个节点的指针。
算法流程:
dfs(cur): 递归法中序遍历;
1)终止条件: 当节点 curcur 为空,代表越过叶节点,直接返回;
2)递归左子树,即 dfs(cur.left) ;
3)构建链表:
1.当 prepre 为空时: 代表正在访问链表头节点,记为 headhead 。
2.当 prepre 不为空时: 修改双向节点引用,即 pre.right = curpre.right=cur , cur.left = precur.left=pre ;
3.保存 curcur : 更新 pre = curpre=cur ,即节点 curcur 是后继节点的 prepre ;
4)递归右子树,即 dfs(cur.left) ;
treeToDoublyList(root):
1)特例处理: 若节点 rootroot 为空,则直接返回;
2)初始化: 空节点 prepre ;
3)转化为双向链表: 调用 dfs(root) ;
4)构建循环链表: 中序遍历完成后,headhead 指向头节点, prepre 指向尾节点,因此修改 headhead 和 prepre 的双向节点引用即可。
5)返回值: 返回链表的头节点 headhead 即可。
代码实现:
//递归解法1
class Solution {
Node pre, head;
public Node treeToDoublyList(Node root) {
if(root == null) return null;
dfs(root);
head.left = pre;
pre.right = head;
return head;
}
void dfs(Node cur) {
if(cur == null) return;
dfs(cur.left);
if(pre != null) pre.right = cur;
else head = cur;
cur.left = pre;
pre = cur;
dfs(cur.right);
}
}
//递归解法2
public class Solution {
public TreeNode Convert(TreeNode pRootOfTree) {
if(pRootOfTree == null) return null;
if(pRootOfTree.left==null&&pRootOfTree.right==null) return pRootOfTree ;
TreeNode left = Convert(pRootOfTree.left);
TreeNode p = left;
while(p!=null&&p.right!=null){
p = p.right;
}
if(left!=null){
p.right = pRootOfTree;
pRootOfTree.left = p;
}
TreeNode right = Convert(pRootOfTree.right);
if(right != null){
pRootOfTree .right = right;
right.left = pRootOfTree ;
}
return left!=null?left:pRootOfTree ;
}
}
//非递归解法
public class Solution {
public TreeNode Convert(TreeNode pRootOfTree) {
TreeNode p = pRootOfTree, pre = null, res = null;
while (p != null) {
while (p.left != null) {
TreeNode q = p.left;
while (q.right != null) {
q = q.right;
}
q.right = p;
TreeNode tmp = p.left;
p.left = null;
p = tmp;
}
p.left = pre;
if (pre == null) {
res = p;
} else {
pre.right = p;
}
pre = p;
p = p.right;
}
return res;
}
}
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]
示例 1:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
示例 2:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。
代码实现:
//从根节点开始向下遍历,用哈希表记录每个节点的父节点;然后p、q分别向上遍历,第一个重叠的节点即为所求
class Solution {
Map parent = new HashMap();
Set visited = new HashSet();
public void dfs(TreeNode root) {
if (root.left != null) {
parent.put(root.left.val, root);
dfs(root.left);
}
if (root.right != null) {
parent.put(root.right.val, root);
dfs(root.right);
}
}
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
dfs(root);
while (p != null) {
visited.add(p.val);
p = parent.get(p.val);
}
while (q != null) {
if (visited.contains(q.val)) {
return q;
}
q = parent.get(q.val);
}
return null;
}
}
序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。
请设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。
示例:
你可以将以下二叉树:
1
/ \
2 3
/ \
4 5
序列化为 "[1,2,3,null,null,4,5]"
代码实现:
public class Codec {
// Encodes a tree to a single string.
public String serialize(TreeNode root) {
return rserialize(root,"");
}
public String rserialize(TreeNode root,String str){
if(root==null)
str += "None,";
else{
str += String.valueOf(root.val)+",";
str = rserialize(root.left,str);
str = rserialize(root.right,str);
}
return str;
}
// Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
String[] str = data.split(",");
List list = new LinkedList(Arrays.asList(str));
return rdeserialize(list);
}
public TreeNode rdeserialize(List list){
if(list.get(0).equals("None")){
list.remove(0);
return null;
}
TreeNode root = new TreeNode(Integer.valueOf(list.get(0)));
list.remove(0);
root.left = rdeserialize(list);
root.right = rdeserialize(list);
return root;
}
}
输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。
示例:
给定如下二叉树,以及目标和 sum = 22,
5
/ \
4 8
/ / \
11 13 4
/ \ / \
7 2 5 1
返回:
[
[5,4,11,2],
[5,8,4,5]
]
代码实现:
//回溯算法+DFS实现
class Solution {
List> lists = new ArrayList>();
List list = new ArrayList();
public List> pathSum(TreeNode root, int sum) {
if(root==null) return lists;
list.add(root.val);
sum -= root.val;
if(sum==0 && root.left==null && root.right==null){
lists.add(new ArrayList(list));
}
pathSum(root.left,sum);
pathSum(root.right,sum);
list.remove(list.size()-1);
return lists;
}
}
请完成一个函数,输入一个二叉树,该函数输出它的镜像。
例如输入:
4
/ \
2 7
/ \ / \
1 3 6 9
镜像输出:
4
/ \
7 2
/ \ / \
9 6 3 1
示例 1:
输入:root = [4,2,7,1,3,6,9]
输出:[4,7,2,9,6,3,1]
代码实现:
方法一:递归法
根据二叉树镜像的定义,考虑递归遍历(dfs)二叉树,交换每个节点的左 / 右子节点,即可生成二叉树的镜像。
递归解析:
终止条件: 当节点 root 为空时(即越过叶节点),则返回 null;
递推工作:
1)初始化节点 tmp ,用于暂存 root的左子节点;
2)开启递归 右子节点 mirrorTree(root.right),并将返回值作为 root 的 左子节点 。
3)开启递归 左子节点 mirrorTree(tmp),并将返回值作为 root 的 右子节点 。
4)返回值: 返回当前节点 root ;
class Solution {
public TreeNode mirrorTree(TreeNode root) {
if(root == null) return null;
TreeNode tmp = root.left;
root.left = mirrorTree(root.right);
root.right = mirrorTree(tmp);
return root;
}
}
方法二:辅助栈(或队列)
利用栈(或队列)遍历树的所有节点 node,并交换每个 node 的左 / 右子节点。
算法流程:
1)特例处理: 当 root 为空时,直接返回 null ;
2)初始化: 栈(或队列),本文用栈,并加入根节点 root 。
3)循环交换: 当栈 stack为空时跳出;
1.出栈: 记为 node ;
2.添加子节点: 将 node 左和右子节点入栈;
3.交换: 交换 node 的左 / 右子节点。
4)返回值: 返回根节点 root 。
class Solution {
public TreeNode mirrorTree(TreeNode root) {
if(root == null) return null;
Stack stack = new Stack<>() {{ add(root); }};
while(!stack.isEmpty()) {
TreeNode node = stack.pop();
if(node.left != null) stack.add(node.left);
if(node.right != null) stack.add(node.right);
TreeNode tmp = node.left;
node.left = node.right;
node.right = tmp;
}
return root;
}
}
请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。
例如,二叉树 [1,2,2,3,4,4,3] 是对称的。
1
/ \
2 2
/ \ / \
3 4 4 3
但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:
1
/ \
2 2
\ \
3 3
示例 1:
输入:root = [1,2,2,3,4,4,3]
输出:true
示例 2:
输入:root = [1,2,2,null,3,null,3]
输出:false
代码实现:
//递归解法
class Solution {
public boolean isSymmetric(TreeNode root) {
return root == null ? true : recur(root.left, root.right);
}
boolean recur(TreeNode L, TreeNode R) {
if(L == null && R == null) return true;
if(L == null || R == null || L.val != R.val) return false;
return recur(L.left, R.right) && recur(L.right, R.left);
}
}