二叉树高频算法题
以下算法题均为力扣原题,对自己刷过的一些二叉树相关算法题做一个总结,便于日后复习。
目录
定义一棵二叉树:
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
1.双遍历序列构造二叉树
1.1.从前序遍历和中序遍历构造二叉树
根据一棵树的前序遍历与中序遍历构造二叉树。
注意:
你可以假设树中没有重复的元素。
例如,给出
前序遍历 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;
}
1.2.从中序遍历和后序遍历构造二叉树
根据一棵树的中序遍历与后序遍历构造二叉树。
注意:
你可以假设树中没有重复的元素。
例如,给出
中序遍历 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;
}
}
1.3.从前序遍历和后序遍历构造二叉树
返回与给定的前序和后序遍历匹配的任何二叉树。
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;
}
}
2.单遍历序列还原二叉树
2.1.从先序遍历还原二叉树
我们从二叉树的根节点 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(;cur<nodes.length&&nodes[cur]!='-';cur++)
val=val*10+nodes[cur]-'0';
curD = 0;
for(;cur<nodes.length&&nodes[cur]=='-';cur++,curD++);
TreeNode r = new TreeNode(val);
if(curD>depth) 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(cur<ch.length && ch[cur]!='-'){
value = value*10+(ch[cur]-'0');
cur++;
}
curD = 0;
while(cur<ch.length && ch[cur]=='-'){
curD++;
cur++;
}
TreeNode root = new TreeNode(value);
if(curD>depth){
root.left = dfs(curD,ch);
}
if(curD>depth){
root.right = dfs(curD,ch);
}
return root;
}
}
2.2 层序遍历还原二叉树
已知一个完全二叉树层序遍历后的数组或list列表,还原该二叉树。
代码实现:
public TreeNode getTree(List<Integer> list){
TreeNode p = new TreeNode(list.get(0));
TreeNode q = p;
Queue<TreeNode> queue = new LinkedList<>();
int i=0;
while(p!=null){
if(2*i+1<list.size()){
p.left = new TreeNode(list.get(2*i+1));
queue.add(p.left);
}
if(2*i+2<list.size()){
p.right = new TreeNode(list.get(2*i+2));
queue.add(p.right);
}
p = queue.poll();
i += 1;
}
return q;
}
进阶:上下翻转/颠倒二叉树
(力扣156)给定一个二叉树,其中所有的右节点要么是具有兄弟节点(拥有相同父节点的左节点)的叶节点,要么为空,将此二叉树上下翻转并将它变成一棵树, 使得原来的左节点成为新的根,原来的右节点将转换成左叶节点,原来的父节点转换为右子节点。返回新的根。
例子:
- 输入: [1,2,3,4,5]
1
/ \
2 3
/ \
4 5
- 输出: 返回二叉树的根 [4,5,2,#,#,3,1]
4
/ \
5 2
/ \
3 1
注意:用例输入为一个层序遍历后的数组,要求输出新树层序遍历后的数组。例如:
- 输入:1,2,3,4,5,6,7
- 输出:4,5,2,3,6,7,1
代码实现:
import java.util.*;
//定义树结构
class TreeNode{
int val;
TreeNode left;
TreeNode right;
TreeNode(int x){
val = x;
}
}
public class Main{
public static void main(String[] args){
Scanner in = new Scanner(System.in);
List<Integer> list = new ArrayList();
String[] str = in.next().split(",");
for(int i=0;i<str.length;i++){
int num = Integer.valueOf(str[i]);
list.add(num);
}
TreeNode root1 = getTree(list);
TreeNode root = upSideDown(root1);
//二叉树层序遍历,输出为一个一维数组
Queue<TreeNode> q = new LinkedList<>();
q.add(root);
ArrayList<Integer> ans = new ArrayList();
while(!q.isEmpty()){
TreeNode node = q.poll();
ans.add(node.val);
if(node.left!=null) q.add(node.left);
if(node.right!=null) q.add(node.right);
}
for(int i=0;i<ans.size()-1;i++){
System.out.print(ans.get(i)+",");
}
System.out.println(ans.get(ans.size()-1));
}
//层序遍历还原二叉树
public static TreeNode getTree(List<Integer> list){
TreeNode p = new TreeNode(list.get(0));
TreeNode q = p;
Queue<TreeNode> queue = new LinkedList<>();
int i=0;
while(p!=null){
if(2*i+1<list.size()){
p.left = new TreeNode(list.get(2*i+1));
queue.add(p.left);
}
if(2*i+2<list.size()){
p.right = new TreeNode(list.get(2*i+2));
queue.add(p.right);
}
p = queue.poll();
i += 1;
}
return q;
}
//将原来的二叉树上下翻转/颠倒使其形成一棵新的二叉树
public static TreeNode upSideDown(TreeNode root){
if(root==null || root.left==null){
return root;
}
TreeNode newRoot = upSideDown(root.left);
TreeNode rRoot = newRoot;
while(rRoot.right!=null){
rRoot = rRoot.right;
}
rRoot.left = root.right;
rRoot.right = new TreeNode(root.val);
return newRoot;
}
}
3.恢复二叉搜索树
二叉搜索树中的两个节点被错误地交换。
请在不改变其结构的情况下,恢复这棵树。
示例 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<TreeNode> list = new ArrayList();
dfs(root,list);
TreeNode x = null;
TreeNode y = null;
for(int i=0;i<list.size()-1;i++){
if(list.get(i).val>list.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<TreeNode> list){
if(root==null) return;
dfs(root.left,list);
list.add(root);
dfs(root.right,list);
}
}
4.将有序数组转化为二叉搜索树
将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。
本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 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;
}
}
5.把二叉搜索树转换为累加树
给定一个二叉搜索树(Binary Search Tree),把它转换成为累加树(Greater Tree),使得每个节点的值是原来的节点值加上所有大于它的节点值之和。
例如:
输入: 原始二叉搜索树:
5
/ \
2 13
输出: 转换为累加树:
18
/ \
20 13
解题思路:反序中序遍历。
本题中要求我们将每个节点的值修改为原来的节点值加上所有大于它的节点值之和。这样我们只需要反序中序遍历该二叉搜索树,记录过程中的节点值之和,并不断更新当前遍历到的节点的节点值,即可得到题目要求的累加树。
代码实现:
class Solution {
int sum = 0;
public TreeNode convertBST(TreeNode root) {
if (root != null) {
convertBST(root.right);
sum += root.val;
root.val = sum;
convertBST(root.left);
}
return root;
}
}
6.合法二叉搜索树
实现一个函数,检查一棵二叉树是否为二叉搜索树。
示例 1:
输入:
2
/ \
1 3
输出: true
示例 2:
输入:
5
/ \
1 4
/ \
3 6
输出: false
解释: 输入为: [5,1,4,null,null,3,6]。根节点的值为 5 ,但是其右子节点值为 4 。
代码实现:
/**解法一:
设计一个递归函数 helper(root, lower, upper) 来递归判断,函数表示考虑以 root 为根的子树,判断子树中所有节点的值
是否都在 (l,r) 的范围内(注意是开区间)。如果 root 节点的值 val 不在(l,r) 的范围内说明不满足条件直接返回,
否则我们要继续递归调用检查它的左右子树是否满足,如果都满足才说明这是一棵二叉搜索树。
那么根据二叉搜索树的性质,在递归调用左子树时,我们需要把上界 upper 改为 root.val,
即调用 helper(root.left, lower, root.val),因为左子树里所有节点的值均小于它的根节点的值。同理递归调用右子树时,
我们需要把下界 lower 改为 root.val,即调用 helper(root.right, root.val, upper)。
*/
class Solution {
public boolean isValidBST(TreeNode root) {
return helper(root,null,null);
}
public boolean helper(TreeNode root,Integer low,Integer high){
if(root==null) return true;
if(low!=null && root.val<=low) return false;
if(high!=null && root.val>=high) return false;
return helper(root.left,low,root.val) && helper(root.right,root.val,high);
}
}
//解法二:中序遍历进行判断
class Solution {
public boolean isValidBST(TreeNode root) {
Stack<TreeNode> stack = new Stack();
double inorder = - Double.MAX_VALUE;
while (!stack.isEmpty() || root != null) {
while (root != null) {
stack.push(root);
root = root.left;
}
root = stack.pop();
// 如果中序遍历得到的节点的值小于等于前一个 inorder,说明不是二叉搜索树
if (root.val <= inorder) return false;
inorder = root.val;
root = root.right;
}
return true;
}
}
7.平衡二叉树
给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过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;
}
}
8.二叉树的最小深度
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [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;
}
}
9.二(N)叉树的最大深度
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
//二叉树的最大深度
class Solution {
public int maxDepth(TreeNode root) {
if(root==null){
return 0;
}
return Math.max(maxDepth(root.left),maxDepth(root.right))+1;
}
}
//N叉树的最大深度
class Node {
public int val;
public List<Node> children;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, List<Node> _children) {
val = _val;
children = _children;
}
};
*/
class Solution {
public int maxDepth(Node root) {
if(root==null) return 0;
int maxChildrenDepth = 0;
for(Node node:root.children){
int max = maxDepth(node);
maxChildrenDepth = Math.max(max,maxChildrenDepth);
}
return maxChildrenDepth+1;
}
}
10.二叉树的最大宽度
给定一个二叉树,编写一个函数来获取这个树的最大宽度。树的宽度是所有层中的最大宽度。这个二叉树与满二叉树(full binary tree)结构相同,但一些节点为空。
每一层的宽度被定义为两个端点(该层最左和最右的非空节点,两端点间的null节点也计入长度)之间的长度。
示例 1:
输入:
1
/ \
3 2
/ \ \
5 3 9
输出: 4
解释: 最大值出现在树的第 3 层,宽度为 4 (5,3,null,9)。
示例 2:
输入:
1
/
3
/ \
5 3
输出: 2
解释: 最大值出现在树的第 3 层,宽度为 2 (5,3)。
示例 3:
输入:
1
/ \
3 2
/
5
输出: 2
解释: 最大值出现在树的第 2 层,宽度为 2 (3,2)。
解题思路:
题目中定义的宽度,刚好对应完全二叉树的特性,每一层的层宽,等于完全二叉树中对应节点的编号差,以题目中的 case 作为示例
1
/ \
3 2
/ \ \
5 3 9
节点在满二叉树中的编号值
0
/ \
1 2
/ \ \
3 4 6
很明显 层宽 = 每一层的最右侧编号 - 最左侧编号 + 1
下面的代码,投机取巧的地方在于,直接原地修改节点的 val 用来存储满二叉树中的编号
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public int widthOfBinaryTree(TreeNode root) {
if(root == null) {
return 0;
}
Deque<TreeNode> queue = new LinkedList<>();
// 根节点编号为 0
root.val = 0;
queue.add(root);
int sum;
int ans = 0;
while(!queue.isEmpty()) {
sum = queue.size();
// 队头和队尾的编号值求差用来更新宽度
ans = Math.max(ans, queue.getLast().val - queue.getFirst().val + 1);
// 一次处理一层,进入这个循环前队列中是一层的所有非空节点
while(sum > 0) {
TreeNode temp = queue.remove();
// 子节点入队前修改 val, val = 满二叉树中节点编号
if(temp.left != null) {
queue.add(temp.left);
temp.left.val = temp.val * 2 + 1;//左节点编号
}
if(temp.right != null) {
queue.add(temp.right);
temp.right.val = temp.val * 2 + 2;//右节点编号
}
sum--;
}
}
return ans;
}
}
11.二叉树的最大路径和
给定一个非空二叉树,返回其最大路径和。
本题中,路径被定义为一条从树中任意节点出发,达到任意节点的序列。该路径至少包含一个节点,且不一定经过根节点。
示例 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);
}
}
11.最长同值路径
(力扣687)给定一个二叉树,找到最长的路径,这个路径中的每个节点具有相同值。 这条路径可以经过也可以不经过根节点。
注意:两个节点之间的路径长度由它们之间的边数表示。
示例 1:
- 输入:
5
/ \
4 5
/ \ \
1 1 5
- 输出: 2
示例 2:
- 输入:
1
/ \
4 5
/ \ \
4 4 5
- 输出: 2
代码实现:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
//https://leetcode-cn.com/problems/longest-univalue-path/solution/guan-yu-di-gui-si-lu-de-chao-xiang-xi-ge-ren-jian-/
//写递归程序,重要的是四点,输入参数、输出结果、函数的功能、终止条件。把这四点写明白,递归程序就写成了。其实递归本身就是完成一种功能的形式化描述。再往大点说,我们写的程序也是形式化描述。计算机又不懂你这背后的逻辑是什么,它只是按照指令的形式去执行而已。所以把递归函数的功能搞明白,就成功了一大半。
class Solution {
int ans;
public int longestUnivaluePath(TreeNode root) {
ans = 0;
longestPath(root);
return ans;
}
//递归函数功能:搜寻以node为起点的最长同值路径:要么是以node为起点的左子树,要么是以node为起点的右子树
public int longestPath(TreeNode node) {
if (node == null) return 0;
int maxLorRres=0;
int left = longestPath(node.left); //node左子树的最长同值路径
int right = longestPath(node.right);//node右子树的最长同值路径
//这种情况对于寻找最长同值路径长有帮助,对于搜索以root为路径起始点的最长路径没有帮助
if (node.left != null && node.left.val == node.val&&node.right != null && node.right.val == node.val) {
ans=Math.max(ans, left + right+2);
}
//从左右子树中选择最长的同值路径
if(node.left!=null&&node.left.val == node.val){
maxLorRres=left+1;
}
if(node.right!=null&&node.right.val==node.val){
maxLorRres=Math.max(maxLorRres,right+1);
}
//从ans与maxLorRres中更新最大值
ans=Math.max(ans,maxLorRres);
return maxLorRres; //所以你能知道为什么返回这个值了吗?
}
//我们递归函数的功能其实就是从左子树和右子树中返回一个最大的同值路径,这也就是为什么我们return的是maxLorRres,即node左子树或右子树的最长同值路径。
}
12.二叉树展开为链表
给定一个二叉树,原地将它展开为一个单链表。
例如,给定二叉树
1
/ \
2 5
/ \ \
3 4 6
将其展开为:
1
\
2
\
3
\
4
\
5
\
6
代码实现:
class Solution {
public void flatten(TreeNode root) {
if (root == null){
return;
}
Stack<TreeNode> s = new Stack<TreeNode>();
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;
/********************************/
}
}
}
13.有序链表转换为二叉搜索树
给定一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树。
本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 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<Integer> list = new ArrayList();
while(head!=null){
list.add(head.val);
head = head.next;
}
return helper(list,0,list.size()-1);
}
public TreeNode helper(List<Integer> 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;
}
}
14.二叉搜索树与双向链表
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。
为了让您更好地理解问题,以下面的二叉搜索树为例:
我们希望将这个二叉搜索树转化为双向循环链表。链表中的每个节点都有一个前驱和后继指针。对于双向循环链表,第一个节点的前驱是最后一个节点,最后一个节点的后继是第一个节点。
下图展示了上面的二叉搜索树转化成的链表。“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;
}
}
15.二叉树的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 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。因为根据定义最近公共祖先节点可以为节点本身。
代码实现:
//解法一:递归查询
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root==null || p==root || q==root)
return root;
TreeNode left = lowestCommonAncestor(root.left,p,q);
TreeNode right = lowestCommonAncestor(root.right,p,q);
if(left==null)
return right;
if(right==null)
return left;
return root;
}
}
public class Solution {
/**
*
* @param root TreeNode类
* @param o1 int整型
* @param o2 int整型
* @return int整型
*/
public int lowestCommonAncestor (TreeNode root, int o1, int o2) {
if(root==null) return -1;
if(o1==root.val || o2==root.val) return root.val;
int left = lowestCommonAncestor(root.left,o1,o2);
int right = lowestCommonAncestor(root.right,o1,o2);
if(left==-1) return right;
if(right==-1) return left;
return root.val;
}
}
//解法二:从根节点开始向下遍历,用哈希表记录每个节点的父节点;然后p、q分别向上遍历,第一个重叠的节点即为所求
class Solution {
Map<Integer, TreeNode> parent = new HashMap<Integer, TreeNode>();
Set<Integer> visited = new HashSet<Integer>();
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;
}
}
//如果题中的二叉树改为二叉搜索树,则递归代码可进一步简化如下
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root.val<p.val && root.val<q.val) return lowestCommonAncestor(root.right,p,q); //结点都在右子树
if(root.val>p.val && root.val>q.val) return lowestCommonAncestor(root.left,p,q); //结点都在左子树
return root;
}
}
16.二叉树的序列化与反序列化
序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。
请设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。
示例:
你可以将以下二叉树:
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<String> list = new LinkedList<String>(Arrays.asList(str));
return rdeserialize(list);
}
public TreeNode rdeserialize(List<String> 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;
}
}
17.二叉树中和为某一值的路径
输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。
示例:
给定如下二叉树,以及目标和 sum = 22,
5
/ \
4 8
/ / \
11 13 4
/ \ / \
7 2 5 1
返回:
[
[5,4,11,2],
[5,8,4,5]
]
代码实现:
//回溯算法+DFS实现
class Solution {
List<List<Integer>> lists = new ArrayList<List<Integer>>();
List<Integer> list = new ArrayList();
public List<List<Integer>> 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;
}
}
18.二叉树的镜像/翻转二叉树
请完成一个函数,输入一个二叉树,该函数输出它的镜像。
例如输入:
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<TreeNode> 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;
}
}
19.对称的二叉树
请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。
例如,二叉树 [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);
}
}
20.左叶子之和
(力扣404)计算给定二叉树的所有左叶子之和。
示例:
3
/ \
9 20
/ \
15 7
在这个二叉树中,有两个左叶子,分别是 9 和 15,所以返回 24
题解:一个节点为「左叶子」节点,当且仅当它是某个节点的左子节点,并且它是一个叶子结点。因此我们可以考虑对整棵树进行遍历,当我们遍历到节点 node时,如果它的左子节点是一个叶子结点,那么就将它的左子节点的值累加计入答案。
遍历整棵树的方法有深度优先搜索和广度优先搜索,下面分别给出了实现代码。
//解法一:深度优先遍历
class Solution {
public int sumOfLeftLeaves(TreeNode root) {
return root != null ? dfs(root) : 0;
}
public int dfs(TreeNode node) {
int ans = 0;
if (node.left != null) {
ans += isLeafNode(node.left) ? node.left.val : dfs(node.left);
}
if (node.right != null && !isLeafNode(node.right)) {
ans += dfs(node.right);
}
return ans;
}
public boolean isLeafNode(TreeNode node) {
return node.left == null && node.right == null;
}
}
//解法二:广度优先遍历
class Solution {
public int sumOfLeftLeaves(TreeNode root) {
if (root == null) {
return 0;
}
Queue<TreeNode> queue = new LinkedList<TreeNode>();
queue.offer(root);
int ans = 0;
while (!queue.isEmpty()) {
TreeNode node = queue.poll();
if (node.left != null) {
if (isLeafNode(node.left)) {
ans += node.left.val;
} else {
queue.offer(node.left);
}
}
if (node.right != null) {
if (!isLeafNode(node.right)) {
queue.offer(node.right);
}
}
}
return ans;
}
public boolean isLeafNode(TreeNode node) {
return node.left == null && node.right == null;
}
}
//以上两种解法,时间、空间复杂度均为O(n)。
21. 二叉树的所有路径
(力扣257)给定一个二叉树,返回所有从根节点到叶子节点的路径。
说明: 叶子节点是指没有子节点的节点。
示例:
- 输入:
1
/ \
2 3
\
5
- 输出: ["1->2->5", "1->3"]
解释: 所有根节点到叶子节点的路径为: 1->2->5, 1->3
解题思路:最直观的方法是使用深度优先搜索。在深度优先搜索遍历二叉树时,我们需要考虑当前的节点以及它的孩子节点。
如果当前节点不是叶子节点,则在当前的路径末尾添加该节点,并继续递归遍历该节点的每一个孩子节点。
如果当前节点是叶子节点,则在当前路径末尾添加该节点后我们就得到了一条从根节点到叶子节点的路径,将该路径加入到答案即可。
如此,当遍历完整棵二叉树以后我们就得到了所有从根节点到叶子节点的路径。
代码实现:
//深度优先搜索
class Solution {
public List<String> binaryTreePaths(TreeNode root) {
List<String> paths = new ArrayList<String>();
constructPaths(root, "", paths);
return paths;
}
public void constructPaths(TreeNode root, String path, List<String> paths) {
if (root != null) {
StringBuffer pathSB = new StringBuffer(path);
pathSB.append(Integer.toString(root.val));//也可用String.valueOf()方法
if (root.left == null && root.right == null) { // 当前节点是叶子节点
paths.add(pathSB.toString()); // 把路径加入到答案中
} else {
pathSB.append("->"); // 当前节点不是叶子节点,继续递归遍历
constructPaths(root.left, pathSB.toString(), paths);
constructPaths(root.right, pathSB.toString(), paths);
}
}
}
}
//广度优先搜索
class Solution {
public List<String> binaryTreePaths(TreeNode root) {
List<String> paths = new ArrayList<String>();
if (root == null) {
return paths;
}
Queue<TreeNode> nodeQueue = new LinkedList<TreeNode>();
Queue<String> pathQueue = new LinkedList<String>();
nodeQueue.offer(root);
pathQueue.offer(Integer.toString(root.val));
while (!nodeQueue.isEmpty()) {
TreeNode node = nodeQueue.poll();
String path = pathQueue.poll();
if (node.left == null && node.right == null) {
paths.add(path);
} else {
if (node.left != null) {
nodeQueue.offer(node.left);
pathQueue.offer(new StringBuffer(path).append("->").append(node.left.val).toString());
}
if (node.right != null) {
nodeQueue.offer(node.right);
pathQueue.offer(new StringBuffer(path).append("->").append(node.right.val).toString());
}
}
}
return paths;
}
}
22.二叉搜索树中的众数
(力扣501)给定一个有相同值的二叉搜索树(BST),找出 BST 中的所有众数(出现频率最高的元素)。
假定 BST 有如下定义:
- 结点左子树中所含结点的值小于等于当前结点的值
- 结点右子树中所含结点的值大于等于当前结点的值
- 左子树和右子树都是二叉搜索树
例如:
给定 BST [1,null,2,2],
1
\
2
/
2
返回[2].
提示:如果众数超过1个,不需考虑输出顺序
进阶:你可以不使用额外的空间吗?(假设由递归产生的隐式调用栈的开销不被计算在内)
题解:我们都知道二叉搜索树的中序遍历是有序的,有一种方式就是把二叉搜索树中序遍历的结果存放到一个数组中,因为这个数组是升序的(虽然有重复的),我们可以遍历这个数组,这里使用几个变量:
使用变量curent表示当前的值,count表示当前值的数量,maxCount表示重复数字最大的数量。list集合存放结果
- 如果节点值等于curent,count就加1,
- 如果节点不等于current,说明遇到了下一个新的值,更新current为新的值,然后让count=1;
接着比较count和maxCount的大小,
- 如果count==maxCount,就把当前节点的值加入到集合list中。
- 如果count>maxCount,先把list集合清空,然后再把当前节点的值加入到集合list中,最后在更新maxCount的值。
如果先把树的节点找出来在计算有点麻烦,我们可以在遍历树的节点的时候就判断,这样就会好一些,下面使用两种方式,一种是递归的,一种是非递归的。树的中序遍历顺序如下:左子节点→当前节点→右子节点
代码实现:
//递归版
class Solution{
List<Integer> mList = new ArrayList<>();
int curent = 0;//表示当前节点的值
int count = 0;//表示当前节点的数量
int maxCount = 0;//最大的重复数量
public int[] findMode(TreeNode root) {
inOrderTraversal(root);
int[] res = new int[mList.size()];
//把集合list转化为数组
for (int i = 0; i < mList.size(); i++) {
res[i] = mList.get(i);
}
return res;
}
//递归方式
public void inOrderTraversal(TreeNode node) {
//终止条件判断
if (node == null)
return;
//遍历左子树
inOrderTraversal(node.left);
//下面是对当前节点的一些逻辑操作
int nodeValue = node.val;
if (nodeValue == curent) {
//如果节点值等于curent,count就加1
count++;
} else {
//否则,就表示遇到了一个新的值,curent和count都要
//重新赋值
curent = nodeValue;
count = 1;
}
if (count == maxCount) {
//如果count == maxCount,就把当前节点加入到集合中
mList.add(nodeValue);
} else if (count > maxCount) {
//否则,当前节点的值重复量是最多的,直接把list清空,然后
//把当前节点的值加入到集合中
mList.clear();
mList.add(nodeValue);
maxCount = count;
}
//遍历右子树
inOrderTraversal(node.right);
}
}
//非递归版
class Solution{
List<Integer> mList = new ArrayList<>();
int curent = 0;
int count = 0;
int maxCount = 0;
public int[] findMode(TreeNode root) {
inOrderTraversal(root);
int[] res = new int[mList.size()];
//把集合list转化为数组
for (int i = 0; i < mList.size(); i++) {
res[i] = mList.get(i);
}
return res;
}
//非递归方式
public void inOrderTraversal(TreeNode tree) {
Stack<TreeNode> stack = new Stack<>();
while (tree != null || !stack.isEmpty()) {
while (tree != null) {
stack.push(tree);
tree = tree.left;
}
if (!stack.isEmpty()) {
tree = stack.pop();
int nodeValue = tree.val;
if (nodeValue == curent) {
//如果节点值等于curent,count就加1
count++;
} else {
//否则,就表示遇到了一个新的值,curent和count都要
//重新赋值
curent = nodeValue;
count = 1;
}
if (count == maxCount) {
//如果count == maxCount,就把当前节点加入到集合中
mList.add(nodeValue);
} else if (count > maxCount) {
//否则,当前节点的值重复量是最多的,直接把list清空,然后
//把当前节点的值加入到集合中
mList.clear();
mList.add(nodeValue);
maxCount = count;
}
tree = tree.right;
}
}
}
}
23.填充每个节点的下一个右侧节点指针II
(力扣117)给定一个二叉树
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL。
进阶:
- 你只能使用常量级额外空间。
- 使用递归解题也符合要求,本题中递归程序占用的栈空间不算做额外的空间复杂度。
思路与算法:这道题希望我们把二叉树各个层的点组织成链表,一个非常直观的思路是层次遍历。树的层次遍历基于广度优先搜索,它按照层的顺序遍历二叉树,在遍历第 iii 层前,一定会遍历完第 i−1i - 1i−1 层。
算法如下:初始化一个队列 qqq,将根结点放入队列中。当队列不为空的时候,记录当前队列大小为 nnn,从队列中以此取出 nnn 个元素并通过这 nnn 个元素拓展新节点。如此循环,直到队列为空。
代码实现:
class Solution {
public Node connect(Node root) {
if (root == null) {
return null;
}
Queue<Node> queue = new LinkedList<Node>();
queue.offer(root);
while (!queue.isEmpty()) {
int n = queue.size();
Node last = null;
for (int i = 0; i < n; ++i) {
Node f = queue.poll();
if (f.left != null) {
queue.offer(f.left);
}
if (f.right != null) {
queue.offer(f.right);
}
if (i != 0) {
last.next = f;
}
last = f;
}
}
return root;
}
}
进阶思路及代码实现:
因为必须处理树上的所有节点,所以无法降低时间复杂度,但是可以尝试降低空间复杂度。
在方法一中,因为对树的结构一无所知,所以使用队列保证有序访问同一层的所有节点,并建立它们之间的连接。然而不难发现:一旦在某层的节点之间建立了 next指针,那这层节点实际上形成了一个链表。因此,如果先去建立某一层的 next指针,再去遍历这一层,就无需再使用队列了。
基于该想法,提出降低空间复杂度的思路:如果第 iii 层节点之间已经建立 next指针,就可以通过 next 指针访问该层的所有节点,同时对于每个第 i 层的节点,我们又可以通过它的 left和 right指针知道其第 i+1层的孩子节点是什么,所以遍历过程中就能够按顺序为第 i+1层节点建立 next 指针。
具体来说:
- 从根节点开始。因为第 0 层只有一个节点,不需要处理。可以在上一层为下一层建立 next指针。该方法最重要的一点是:位于第 x 层时为第 x+1 层建立 next指针。一旦完成这些连接操作,移至第 x+1层为第 x+2 层建立 next 指针。
- 当遍历到某层节点时,该层节点的 next指针已经建立。这样就不需要队列从而节省空间。每次只要知道下一层的最左边的节点,就可以从该节点开始,像遍历链表一样遍历该层的所有节点。
class Solution {
Node last = null, nextStart = null;
public Node connect(Node root) {
if (root == null) {
return null;
}
Node start = root;
while (start != null) {
last = null;
nextStart = null;
for (Node p = start; p != null; p = p.next) {
if (p.left != null) {
handle(p.left);
}
if (p.right != null) {
handle(p.right);
}
}
start = nextStart;
}
return root;
}
public void handle(Node p) {
if (last != null) {
last.next = p;
}
if (nextStart == null) {
nextStart = p;
}
last = p;
}
}
24.二叉搜索树中的插入操作
(力扣701)给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据保证,新值和原始二叉搜索树中的任意节点值都不同。
注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回任意有效的结果。
例如,
给定二叉搜索树:
4
/ \
2 7
/ \
1 3
和 插入的值: 5
你可以返回这个二叉搜索树:
4
/ \
2 7
/ \ /
1 3 5
或者这个树也是有效的:
5
/ \
2 7
/ \
1 3
\
4
思路与算法:首先回顾二叉搜索树的性质:对于任意节点 root 而言,左子树(如果存在)上所有节点的值均小于 root.val,右子树(如果存在)上所有节点的值均大于 root.val,且它们都是二叉搜索树。
因此,当将 val 插入到以 root 为根的子树上时,根据 val 与 root.val 的大小关系,就可以确定要将 val 插入到哪个子树中。
- 如果该子树不为空,则问题转化成了将 val 插入到对应子树上。
- 否则,在此处新建一个以 val 为值的节点,并链接到其父节点 root 上。
代码实现:
class Solution {
public TreeNode insertIntoBST(TreeNode root, int val) {
if (root == null) {
return new TreeNode(val);
}
TreeNode pos = root;
while (pos != null) {
if (val < pos.val) {
if (pos.left == null) {
pos.left = new TreeNode(val);
break;
} else {
pos = pos.left;
}
} else {
if (pos.right == null) {
pos.right = new TreeNode(val);
break;
} else {
pos = pos.right;
}
}
}
return root;
}
}
25.求根到叶子节点的数字之和
(力扣129)给定一个二叉树,它的每个结点都存放一个 0-9 的数字,每条从根到叶子节点的路径都代表一个数字。
例如,从根到叶子节点路径 1->2->3 代表数字 123。
计算从根到叶子节点生成的所有数字之和。
说明: 叶子节点是指没有子节点的节点。
示例 1:
输入: [1,2,3]
1
/ \
2 3
输出: 25
解释:
从根到叶子节点路径 1->2 代表数字 12.
从根到叶子节点路径 1->3 代表数字 13.
因此,数字总和 = 12 + 13 = 25.
示例 2:
输入: [4,9,0,5,1]
4
/ \
9 0
/ \
5 1
输出: 1026
解释:
从根到叶子节点路径 4->9->5 代表数字 495.
从根到叶子节点路径 4->9->1 代表数字 491.
从根到叶子节点路径 4->0 代表数字 40.
因此,数字总和 = 495 + 491 + 40 = 1026.
代码实现:
//思路:深度优先遍历
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public int sumNumbers(TreeNode root) {
return dfs(root,0);
}
public int dfs(TreeNode root,int pre){
if(root==null) return 0;
int sum = pre*10+root.val;
if(root.left==null && root.right==null){
return sum;
}else{
return dfs(root.left,sum) + dfs(root.right,sum);
}
}
}
26.完全二叉树的节点个数
给出一个完全二叉树,求出该树的节点个数。
说明:完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2^h 个节点。
示例:
- 输入:
1
/ \
2 3
/ \ /
4 5 6
- 输出: 6
代码实现:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
//递归方法
class Solution {
public int countNodes(TreeNode root) {
if(root==null) return 0;
int left = countNodes(root.left);
int right = countNodes(root.right);
return left+right+1;
}
}
//层序遍历方法
class Solution {
public int countNodes(TreeNode root) {
if(root==null) return 0;
Queue<TreeNode> queue = new LinkedList<>();
List<Integer> list = new ArrayList();
queue.add(root);
while(!queue.isEmpty()){
int size = queue.size();
for(int i=0;i<size;i++){
TreeNode node = queue.poll();
list.add(node.val);
if(node.left!=null){
queue.add(node.left);
}
if(node.right!=null){
queue.add(node.right);
}
}
}
return list.size();
}
}