二叉树
注意修改深度优先与广度优先,之前理解错误,层序遍历(队列)是广度优先,二叉树遍历(递归,栈+回溯)是深度优先
94.⼆叉树的中序遍历
中序:左中右
前序:中左右
后序:左右中
x序:“中”就在x处。
补充:可能:递归就是深度优先遍历;队列,先进先)+循环迭代就是广度优先遍历
1.递归: //递归函数:用于返回传入节点的中序遍历
public List<Integer> inorderTraversal(TreeNode root) {
// if(root == null) return null;
List<Integer> list = new ArrayList<Integer>();
inorder(root,list);
return list;
}
//递归函数:用于返回传入节点的中序遍历
public void inorder(TreeNode root, List<Integer> list){
if(root == null) return;
inorder(root.left,list);
list.add(root.val);
inorder(root.right,list);
}
回溯算法:
栈+循环迭代:
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
//2.栈+循环迭代
Stack<TreeNode> stack = new Stack();
List<Integer> list = new ArrayList();
while(root != null || !stack.isEmpty()){
while(root != null){
stack.push(root);
root = root.left;
}
root = stack.pop();
list.add(root.val);
root = root.right;
}
return list;
}
}
100.相同的树
深度优先遍历:
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
//递归
if(p == null && q == null){
return true;
}else if(p == null || q == null){
return false;
}else if(p.val == q.val){
return isSameTree(p.right,q.right) && isSameTree(p.left,q.left);
}
return false;
}
}
广度优先遍历
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
//2.广度优先遍历:队列+循环迭代
//一层层加入队列内并弹出做操作,直至队列内为空
//边界判断
if(p == null && q == null){
return true;
}else if(p == null || q == null){
return false;
}
//创建先进先出队列
LinkedList<TreeNode> stack1 = new LinkedList();
LinkedList<TreeNode> stack2 = new LinkedList();
stack1.offer(p);
stack2.offer(q);
TreeNode left1,left2,right1,right2,node1,node2;
while(!stack1.isEmpty() && !stack2.isEmpty()){
node1 = stack1.poll();
node2 = stack2.poll();
left1 = node1.left;
right1 = node1.right;
left2 = node2.left;
right2 = node2.right;
//判断对应位置元素是否符合要求
if(node1.val != node2.val){
return false;
}
//判断并向队列内添加节点
if((left1 == null ^ left2 == null) || (right1 == null ^ right2 == null)){
return false;
}
if(left1 != null){
stack1.offer(left1);
stack2.offer(left2);
}
if(right1 != null){
stack1.offer(right1);
stack2.offer(right2);
}
}
return true;//这里直接return true是因为循环里都是成对往俩个队列内加节点的
}
}
102.⼆叉树的层序遍历
层序遍历:即广度优先遍历//一层层从左往右添加进队列,然后按顺序弹出
层序遍历的⼀般写法,通过⼀个 while 循环控制从上向下⼀层层遍历,for 循环size控制每⼀层从左向右遍 历。但是本题没有对每一层的个数在遍历时进行限制,所以也不需要size来记录每一层的节点个数。
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> list = new ArrayList<List<Integer>>();
//层序遍历:即广度优先遍历
//一层层从左往右添加进队列,然后按顺序弹出
if(root == null){
return new ArrayList<List<Integer>>();
}
LinkedList<TreeNode> queen = new LinkedList();
queen.offer(root);
while(!queen.isEmpty()){
queen = getnextQueen(queen,list);
}
return list;
}
//获取下一层节点返回队列,并将该层节点的val按层加入list
public LinkedList<TreeNode> getnextQueen(LinkedList<TreeNode> q,List<List<Integer>> list){
LinkedList<TreeNode> result = new LinkedList();
List<Integer> l = new ArrayList();
TreeNode node;
//对传入的队列进行遍历获取val和下一层节点
while(!q.isEmpty()){
node = q.poll();
l.add(node.val);
if(node.left != null){
result.offer(node.left);
}
if(node.right != null){
result.offer(node.right);
}
}
list.add(l);//添加传入queen的val
return result;//返回下一层queen
}
}
103.⼆叉树的锯⻮形层序遍历
与102相比区别在于利用了flag来区分此次函数调用是从左往右还是从右往左,同时,需要注意调用函数里的val获取顺序和下一层节点的队列获取顺序是不相同的
class Solution {
public boolean flag = true;
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
List<List<Integer>> list = new ArrayList<List<Integer>>();
//层序遍历:即广度优先遍历
//一层层从左往右添加进队列,然后按顺序弹出
if(root == null){
return new ArrayList<List<Integer>>();
}
LinkedList<TreeNode> queen = new LinkedList();
queen.offer(root);
while(!queen.isEmpty()){
queen = getnextQueen(queen,list);
}
return list;
}
//获取下一层节点返回队列,并将该层节点的val按层加入list
public LinkedList<TreeNode> getnextQueen(LinkedList<TreeNode> q,List<List<Integer>> list){
LinkedList<TreeNode> result = new LinkedList();
List<Integer> l = new ArrayList();
LinkedList<TreeNode> temp = new LinkedList(q);//q用于取val,temp用于removeLast获取下一层节点
TreeNode nodeval,nodelr;
//对传入的队列进行遍历获取val和下一层节点
//利用flag来定义本层取val的顺序和下一层的添加顺序
if(flag == true){
while(!q.isEmpty()){
nodeval = q.poll();
l.add(nodeval.val);
nodelr = temp.removeLast();
if(nodelr.right != null){
result.offer(nodelr.right);
}
if(nodelr.left != null){
result.offer(nodelr.left);
}
}
}else{
while(!q.isEmpty()){
nodeval = q.poll();
l.add(nodeval.val);
nodelr = temp.removeLast();
if(nodelr.left != null){
result.offer(nodelr.left);
}
if(nodelr.right != null){
result.offer(nodelr.right);
}
}
}
flag = !flag;
list.add(l);//添加传入queen的val
return result;//返回下一层queen
}
}
104.⼆叉树的最⼤深度
1.//深度优先遍历,递归
class Solution {
public int maxDepth(TreeNode root) {
return root == null ? 0:Math.max(maxDepth(root.left),maxDepth(root.right))+1;
}
}
这里的广度优先遍历利用size来进行一层层的遍历,size取一层节点的个数。
也是层序遍历思想。
//广度优先遍历,队列+循环
class Solution {
public int maxDepth(TreeNode root) {
LinkedList<TreeNode> queen = new LinkedList();
int depth = 0,size = 0;
if(root == null){
return 0;
}
queen.offer(root);
TreeNode n;
while(!queen.isEmpty()){
size = queen.size();
depth++;
while(size > 0){
n = queen.poll();
size--;
if(n.left != null){
queen.offer(n.left);
}
if(n.right != null){
queen.offer(n.right);
}
}
}
return depth;
}
}
144.⼆叉树的前序遍历
深度优先遍历:递归
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList();
preorder(root,list);
return list;
}
//中左右
public void preorder(TreeNode root,List<Integer> list){
if(root == null) return;
list.add(root.val);
preorder(root.left,list);
preorder(root.right,list);
}
}
这里发现,在前中后序遍历中,递归和迭代都是深度优先遍历。
层序遍历是广度优先遍历。(前面理解有误)。
而且这里用到了回溯思想。即深度优先遍历+回溯。
//2.栈+循环迭代
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList();
Stack<TreeNode> stack = new Stack();
if(root == null){
return list;
}
while(root != null || !stack.isEmpty()){
while(root != null){
stack.push(root);
list.add(root.val);
root = root.left;
}
root = stack.pop();
root = root.right;
}
return list;
}
}
543.⼆叉树的直径
这里的直径可以理解为左右子树的深度之和;但是不是根节点的左右子树高度,而是要计算每个节点的左右子树高度。
在每次调用递归函数时,计算该节点的双子树深度之和用全局变量记录。
class Solution {
public int ans;
//定义该递归函数为获取输入节点为根节点的二叉树的最大直径。
public int diameterOfBinaryTree(TreeNode root) {
depth(root);
return ans;
}
//传入一个节点,计算左右子树深度和.
//即在计算二叉树最大深度的过程中穿插一个类变量来记录最大直径即可。
public int depth(TreeNode node){
if(node == null){
return 0;
}
int dl = depth(node.left);
int dr = depth(node.right);
ans = Math.max(ans,dl + dr);
return Math.max(dl,dr) + 1;
}
}
105.从前序与中序遍历序列构造⼆叉树
构造⼆叉树,第⼀件事⼀定是找根节点,然后想办法构造左右⼦树。
将二叉树看作三部分组成,根节点,左子树,右子树。(子树又由根节点,左子树,右子树组成)
考虑递归方式:递归函数中需要获取传入的前中序数组对应二叉树的根节点。传入参数有前序数组,中序数组,前序数组中的左右子树对应的索引号。
利用前序数组获得根节点,利用中序数组获取左右子树个数。
递归:
class Solution {
public Map<Integer,Integer> hashmap;
public TreeNode buildTree(int[] preorder, int[] inorder) {
hashmap = new HashMap();//hashmap用于记录key = 数值,value = 索引的map,以便在中序数组中快速定位到根节点的索引,从而在中序数组中区分左右子树。
for(int i = 0;i < inorder.length;i++){
hashmap.put(inorder[i],i);
}
return getsubTree(preorder,inorder,0,preorder.length - 1,0,inorder.length - 1);
}
public TreeNode getsubTree(int[] preorder,int[] inorder,int prefirst,int prelast,int infirst,int inlast){
if(prefirst > prelast) {return null;}
TreeNode root = new TreeNode(preorder[prefirst]);
int index = hashmap.get(preorder[prefirst]);
int lenl = index - infirst;//中序数组中确定左子树长度
int lenr = inlast - index;//中序数组中确定右子树长度
root.left = getsubTree(preorder,inorder,prefirst + 1,prelast - lenr,infirst,index - 1);//递归获取左子树根节点,前序左子树区间prefirst + 1,prelast - lenr;中序左子树区间infirst,index - 1
root.right = getsubTree(preorder,inorder,prefirst + 1 + lenl,prelast,index + 1,inlast);//递归获取右子树根节点,前序左子树区间prefirst + 1 + lenl,prelast;中序左子树区间index + 1,inlast
return root;
}
}
106.从中序与后序遍历序列构造⼆叉树
//中序:左中右,后序:左右中。思路同105,确定中间的根节点,找出左右子树,递归。
//由后序数组获取根节点,由中序数组
class Solution {
Map<Integer,Integer> hashmap = new HashMap();
public TreeNode buildTree(int[] inorder, int[] postorder) {
for(int i = 0;i < inorder.length;i++){
hashmap.put(inorder[i],i);
}
return getsubTree(inorder,postorder,0,inorder.length - 1,0,postorder.length - 1);
}
public TreeNode getsubTree(int[] inorder, int[] postorder, int infirst, int inlast, int postfirst, int postlast){
if(postfirst > postlast){return null;}
TreeNode root = new TreeNode(postorder[postlast]);
int index = hashmap.get(postorder[postlast]);
int lenl = index - infirst,lenr = inlast - index;
root.left = getsubTree(inorder,postorder,infirst,index - 1,postfirst,postfirst + lenl - 1);
root.right = getsubTree(inorder,postorder,index + 1,inlast,postfirst + lenl,postlast - 1);
return root;
}
}
654.最⼤⼆叉树
//本题思路可参考105,106.都是二叉树的构造。
//考虑找到根节点,再确定左右子树。
利用getmaxindex获取区间内的数组最大值的索引,这样即可获得最大值,也可省略hashmap的使用。
获取的maxindex既可用于分割左右子树。
class Solution {
public TreeNode constructMaximumBinaryTree(int[] nums) {
return getSubTree(nums,0,nums.length - 1);
}
public TreeNode getSubTree(int[] nums,int first, int last){
if(first > last){return null;}
int maxindex = getmaxindex(nums,first,last);
TreeNode root = new TreeNode(nums[maxindex]);
root.left = getSubTree(nums,first,maxindex - 1);
root.right = getSubTree(nums,maxindex + 1,last);
return root;
}
public int getmaxindex(int[] nums,int first,int last){
int i = first + 1;
int maxindex = first;
while(i <= last){
maxindex = (nums[i] > nums[maxindex] ? i : maxindex);
i++;
}
return maxindex;
}
}
107.⼆叉树的层序遍历 II
小tip:层序遍历用的是队列,先进先出;
深度优先遍历用了回溯算法,即栈,先进后出。
//自底向上从左往右,即自顶而下从右往左,然后再反转。
class Solution {
public List<List<Integer>> levelOrderBottom(TreeNode root) {
LinkedList<TreeNode> queen = new LinkedList();
List<List<Integer>> result = new ArrayList();
TreeNode temp = new TreeNode();
int size;
if(root == null) return result;
queen.offer(root);
while(!queen.isEmpty()){
List<Integer> list = new ArrayList();
size = queen.size();
while(size-- > 0){
temp = queen.poll();
list.add(temp.val);
if(temp.right != null){
queen.offer(temp.right);
}
if(temp.left != null){
queen.offer(temp.left);
}
}
Collections.reverse(list); // Collections.reverse()用于反转list集合
result.add(list);
}
Collections.reverse(result);
return result;
}
}
111.⼆叉树的最⼩深度
//1.深度优先遍历,递归。
//求最小深度与最大深度不同。最小深度的重点是找到左右子节点均为null的叶子节点。
class Solution {
public int minDepth(TreeNode root) {
if(root == null) return 0;
if(root.left == null && root.right == null){
return 1;
}//用于找出叶节点。
Integer mindepth = Integer.MAX_VALUE;
if(root.left != null){
mindepth = Math.min(minDepth(root.left),mindepth);
}
//子树深度
if(root.right != null){
mindepth = Math.min(minDepth(root.right),mindepth);
}
return mindepth + 1;
}
}
二叉树最小深度推荐使用层序遍历。
//2.层序遍历,找到左右子节点均为null的叶子节点即可。
//注意广度优先遍历和层序遍历的区别。
class Solution {
public int minDepth(TreeNode root) {
LinkedList<TreeNode> queen = new LinkedList();
TreeNode temp = new TreeNode();
int size = 0,depth = 0;
if(root == null){
return 0;
}
queen.offer(root);
while(!queen.isEmpty()){
size = queen.size();
depth++;
while(size-- > 0){
temp = queen.poll();
//判断是否为叶子节点,
if(temp.left == null && temp.right == null){
return depth;
}
if(temp.left != null){
queen.offer(temp.left);
}
if(temp.right != null){
queen.offer(temp.right);
}
}
}
return depth;//这一步return其实执行不到,找到叶子节点在循环内。
}
}
114.⼆叉树展开为链表
遍历获取list然后重新构造链表。
class Solution {
public void flatten(TreeNode root) {
List<TreeNode> list = new ArrayList<TreeNode>();
preorderTraversal(root, list);
int size = list.size();
for (int i = 1; i < size; i++) {
TreeNode prev = list.get(i - 1), curr = list.get(i);
prev.left = null;
prev.right = curr;
}
}
public void preorderTraversal(TreeNode root, List<TreeNode> list) {
if (root != null) {
list.add(root);
preorderTraversal(root.left, list);
preorderTraversal(root.right, list);
}
}
}
⼆叉树的递归分为「遍历」和「分解问题」两种思维模式,这道题需要⽤到 「分解问题」的思维。 前者较简单,只要运⽤⼆叉树的递归遍历框架即可;后者的关键在于明确递归函数的定义,然后利⽤这个定 义,这题就属于后者,
flatten 函数的定义如下: 给 flatten 函数输⼊⼀个节点 root,那么以 root 为根的⼆叉树就会被拉平为⼀条链表。
//本题可以看作中,左子树,右子树,转为中-右子树(左子树转变)-原左子树。
//则递归即可看作将传入的root的二叉树转为单链表。
class Solution {
public void flatten(TreeNode root) {
if(root == null) return;
flatten(root.left);
flatten(root.right);
TreeNode Left = root.left;
TreeNode Right = root.right;
root.left = null;
root.right = Left;
TreeNode p = root;//同时解决Left为null的情况
while(p.right != null){
p = p.right;
}
p.right = Right;
}
}
116.填充每个节点的下⼀个右侧节点指针
本题递归思路可以同114,采用递归
明确本题递归函数的含义是将传入的二叉树进行填充完成,然后再递归函数中补充针对已完成填充的左右子树进行填充补充。
class Solution {
public Node connect(Node root) {
if(root == null) return root;
root = getNext(root);
return root;
}
//完成传入节点二叉树的填充
public Node getNext(Node n){
if(n.left == null) return n;
n.left = getNext(n.left);
n.right = getNext(n.right);
n.next = null;
Node temp1 = n.left,temp2 = n.right;
while(temp1 != null){
temp1.next = temp2;
// temp2.next = null;
temp1 = temp1.right;
temp2 = temp2.left;
}
return n;
}
}
//层序遍历,对每一层进行next赋值操作
//本题的填充也可以看作是每一层从左到右的指向
//层序遍历,对每一层进行next赋值操作
//本题的填充也可以看作是每一层从左到右的指向
class Solution {
public Node connect(Node root) {
LinkedList<Node> queen = new LinkedList();
Node temp = new Node();
int size;
if(root == null){
return root;
}
queen.offer(root);
while(!queen.isEmpty()){
size = queen.size();
while(size > 0){
temp = queen.poll();
if(size > 1){
temp.next = queen.peek();
}else{
temp.next = null;
}
if(temp.left != null){
queen.offer(temp.left);
queen.offer(temp.right);
}
size--;
}
}
return root;
}
}
226.翻转⼆叉树
递归:这类题目,也就是**「分解问题」的思维,关键在于明确递归函数的定义**,
这里递归函数是完成传入二叉树的反转,而递归函数内,完成左右子树的反转,拥有跟节点,反转的左子树和右子树,递归中再继续进行传入根节点的反转操作即可。
小结:递归函数:1.完成左右子树的目标操作,2.对已完成操作的左右子树和根节点补充操作,3.设置终止条件。
//1.递归
class Solution {
public TreeNode invertTree(TreeNode root) {
invert(root);
return root;
}
public void invert(TreeNode root){
if(root == null) return;
invert(root.right);
invert(root.left);
TreeNode temp = root.right;
root.right = root.left;
root.left = temp;
}
}
层序遍历;对一层的节点进行其左右子节点的互换,从上至下,直至为null为止。
//2.迭代
class Solution {
public TreeNode invertTree(TreeNode root) {
if(root == null) return null;
LinkedList<TreeNode> queen = new LinkedList();
TreeNode temp = null;
queen.offer(root);
int size;
while(!queen.isEmpty()){
size = queen.size();
for(int i = 0;i < size;i++){
temp = queen.poll();
invert(temp);
queen.offer(temp.left);
queen.offer(temp.right);
}
}
return root;
}
public void invert(TreeNode root){
if(root == null) return;
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
}
}
145.⼆叉树的后序遍历
深度优先遍历,递归.
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList();
postorder(root,list);
return list;
}
//左右中
public void postorder(TreeNode root,List<Integer> list){
if(root == null) return;
postorder(root.left,list);
postorder(root.right,list);
list.add(root.val);
}
}
广度优先遍历,回溯.栈+迭代
本解法的重点在于这段
if(root.right == null || root.right == prev){
list.add(root.val);
prev = root;
root = null;
}else{
stack.push(root);
root = root.right;
}
设置一个prev是为了防止回溯到root.right之后,往上回溯root时,虽然该root存在right,但是已经遍历过了,那么就不去遍历,而是令root = null继续回溯.
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList();
Stack<TreeNode> stack = new Stack();
TreeNode prev = null;
while(root !=null || !stack.isEmpty()){
while(root != null){
stack.push(root);
root = root.left;
}
root = stack.pop();
if(root.right == null || root.right == prev){
list.add(root.val);
prev = root;
root = null;
}else{
stack.push(root);
root = root.right;
}
}
return list;
}
}
222.完全⼆叉树的节点个数
法1.直接遍历统计;
法2:时间复杂度小于O(n):
⼀棵完全⼆叉树的两棵⼦树,⾄少有⼀棵是满⼆叉树,而满二叉树的节点数可由树高获取.
class Solution {
public int countNodes(TreeNode root) {
if(root == null) return 0;
TreeNode p = new TreeNode();
//计算左右子树高度
int ld = 0,rd = 0,Lcount = 0,Rcount = 0;
p = root.left;
while(p != null){
p = p.left;
ld++;
}
p = root.right;
while(p != null){
p = p.left;//这里要注意,右子树的深度也要用.left来计算,因为这是完全二叉树.
rd++;
}
//分左右子树各自为满二叉树的情况递归迭代.
if(ld == rd){
Lcount = (int)Math.pow(2, ld) - 1;
Rcount = countNodes(root.right);
}else{
Lcount = countNodes(root.left);
Rcount = (int)Math.pow(2, rd) - 1;
}
return 1 + Lcount + Rcount;
}
}
236.⼆叉树的最近公共祖先 (重点重点重点)
递归:输入节点,p,q返回节点,分类讨论
// 情况 1,如果 p 和 q 都在以 root 为根的树中,那么 left 和 right ⼀定分别是 p 和 q。
// 情况 2,如果 p 和 q 都不在以 root 为根的树中,直接返回 null。
// 情况 3,如果 p 和 q 只有⼀个存在于 root 为根的树中,函数返回该节点。
这种方法难以理解,可使用第二种方法,利用Map,先遍历所有节点存储每个节点的父节点;
然后以p节点为起始遍历父节点并存于memo2中,然后q遍历父节点,找到第一个存于memo2中的祖先节点
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null) return null;
if(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;
}else if(left == null && right == null){
return null;
}else{
return left == null ? right : left;
}
}
}
341.扁平化嵌套列表迭代器 (陌生陌生陌生)
Iterator:Iterator接口中包含三个基本方法,next(), hasNext(), remove(),
Iterator 是 java.utils 包下定义的迭代器接口。在开发中,我们用它来遍历集合,从而实现访问相应数据以及删除相应数据的目的。
public class NestedIterator implements Iterator<Integer> {
private List<Integer> vals;
private Iterator<Integer> cur;
public NestedIterator(List<NestedInteger> nestedList) {
vals = new ArrayList();
getVals(nestedList);
cur = vals.iterator();
}
public void getVals(List<NestedInteger> nestedList){
for(NestedInteger nest : nestedList){
if(nest.isInteger()){
vals.add(nest.getInteger());
}else{
getVals(nest.getList());
}
}
}
@Override
public Integer next() {
return cur.next();
}
@Override
public boolean hasNext() {
return cur.hasNext();
}
}
501.⼆叉搜索树中的众数
BST 的中序遍历有序,在中序遍历的位置做⼀些判断逻辑和操作有序数组差不多,很容易找出众数。
1.遍历获得集合list然后对list进行操作完成功能
2.利用二叉搜索树的中序遍历有序在遍历过程中的中序处做操作。
//二叉搜索树:左<=中<=右
// BST 的中序遍历有序,在中序遍历的位置做⼀些判断逻辑和操作有序数组.
class Solution {
private int maxcount,nowcount,nowvalue;
public int[] findMode(TreeNode root) {
List<Integer> list = new ArrayList();
ergodic(root,list);
int[] nums = new int[list.size()];
for(int i = 0;i < list.size();i++){
nums[i] = list.get(i);
}
return nums;
}
public void ergodic(TreeNode root,List<Integer> list){
if(root == null) return;
ergodic(root.left,list);
update(root,list);
ergodic(root.right,list);
}
//对有序数组进行操作。
public void update(TreeNode root,List<Integer> list){
if(root.val == nowvalue){
//相等就进行计数+1
nowcount++;
}else{
//不相等就修改计数和当前值
nowcount = 1;
nowvalue = root.val;
}
//每遍历一个数都进行一次计数判断。
if(nowcount > maxcount){
list.clear();
list.add(nowvalue);
maxcount = nowcount;
}else if(nowcount == maxcount){
list.add(nowvalue);
}
}
}
559.N 叉树的最⼤深度
递归:借鉴二叉树最大深度,只不过这里不确定每个节点的子节点有多少,所以要用个循环,遍历子节点深度与max比较。
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 max = 0;
int temp;
for(int i = 0; i < root.children.size();i++){
temp = maxDepth(root.children.get(i));
max = (max < temp ? temp : max);
}
max++;
return max;
}
}
层序遍历
class Solution {
public int maxDepth(Node root) {
//层序遍历
if(root == null) return 0;
LinkedList<Node> queen = new LinkedList();
int result = 0,size;
Node temp;
queen.offer(root);
while(!queen.isEmpty()){
size = queen.size();
while(size-- > 0){
temp = queen.poll();
for(int i = 0;i < temp.children.size();i++){
queen.offer(temp.children.get(i));
}
}
result++;
}
return result;
}
}
589.N 叉树的前序遍历
用for循环代替二叉树的左右子树遍历
深度优先遍历:递归
class Solution {
public List<Integer> preorder(Node root) {
List<Integer> list = new ArrayList();
if(root == null) return list;
pretool(root,list);
return list;
}
public void pretool(Node root,List<Integer> list){
list.add(root.val);
for(Node child : root.children){
pretool(child,list);
}
}
}
广度优先遍历:栈+回溯:——较复杂。
590.N 叉树的后序遍历
深度优先遍历:递归
class Solution {
public List<Integer> postorder(Node root) {
List<Integer> list = new ArrayList();
// if(root == null) return list;
posttool(root,list);
return list;
}
public void posttool(Node root,List<Integer> list){
if(root == null) return;
for(int i = 0;i < root.children.size();i++){
posttool(root.children.get(i),list);
}
list.add(root.val);
}
}
965.单值⼆叉树
1.暴力:遍历得list
2.递归:
class Solution {
public boolean isUnivalTree(TreeNode root) {
// if(root == null) return true;
return issame(root,root.val);
}
public boolean issame(TreeNode root,int num){
if(root == null) return true;
if(root.val != num){
return false;
}
return issame(root.left,num) && issame(root.right,num);
}
}
297.⼆叉树的序列化与反序列化**(difficulty)**
序列化问题其实就是遍历问题,遍历,顺⼿把遍历的结果转化成字符串的形式,就是序列化.
public class Codec {
// Encodes a tree to a single string.
public String serialize(TreeNode root) {
return serergodic(root,"");
}
public String serergodic(TreeNode root,String s){
if(root == null){
s += "None,";
}else{
s += (String.valueOf(root.val) + ",");
s = serergodic(root.left,s);
s = serergodic(root.right,s);
}
return s;
}
// Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
// TreeNode root = new TreeNode();
String[] datas = data.split(",");
List<String> dataqueen = new LinkedList<String>(Arrays.asList(datas));
return desergodic(dataqueen);
}
public TreeNode desergodic(List<String> dataqueen){
if(dataqueen.get(0).equals("None")){
dataqueen.remove(0);
return null;
}
TreeNode root = new TreeNode(Integer.valueOf(dataqueen.get(0)));
dataqueen.remove(0);
root.left = desergodic(dataqueen);
root.right = desergodic(dataqueen);
return root;
}
}
652.寻找重复的⼦树
如果你想知道以⾃⼰为根的⼦树是不是重复的,是否应该被加⼊结果列表中,你需要知道什么信息? 你需要知道以下两点:
1、以我为根的这棵⼆叉树(⼦树)⻓啥样?
2、以其他节点为根的⼦树都⻓啥样?
前⽂ 序列化和反序列化⼆叉树 其实写过了,⼆叉树的前序/中序/ 后序遍历结果可以描述⼆叉树的结构。
利用hashmap保存以当前节点为根节点的二叉树的序列化
//利用hashmap保存以当前节点为根节点的二叉树的序列化
class Solution {
public List<TreeNode> findDuplicateSubtrees(TreeNode root) {
Map<String,Integer> hashmap = new HashMap();
List<TreeNode> list = new ArrayList();
ergodic(root,hashmap,list);
return list;
}
//前序遍历,构建hashmap。
public void ergodic(TreeNode root,Map<String,Integer> hashmap,List<TreeNode> list){
if(root == null) return;
String temp = serialize(root,"");
if(!hashmap.containsKey(temp)){
hashmap.put(temp,1);
}else{
hashmap.put(temp,hashmap.get(temp) + 1);
if(hashmap.get(temp) == 2){
list.add(root); // 只要出现过2次,就属于结果list,限制在==2是因为大于2次我们只应该记录一次。
}
}
ergodic(root.left,hashmap,list);
ergodic(root.right,hashmap,list);
}
//获取该子树的序列化字符串
public String serialize(TreeNode root,String s){
if(root == null){
return s += "None,";
}else{
s += String.valueOf(root.val) + ",";
s = serialize(root.left,s);
s = serialize(root.right,s);
}
return s;
}
}
二叉搜索树
95.不同的⼆叉搜索树 II
二叉搜索树的特点,左子树值 < 中 < 右子树值,所以只需要选定root为i,则(0,i-1)构造左子树,(i+1,n)构造右子树
1、穷举 root 节点的所有可能。 2、递归构造出左右⼦树的所有合法 BST。 3、给 root 节点穷举所有左右⼦树的组合。
递归函数构造lo-hi之间的所有BST集合,终止条件是递归至最终,必然形成lo > hi;
(递归函数的终止条件可按自己递归的输入归至最后的情况来设置终止条件)
class Solution {
public List<TreeNode> generateTrees(int n) {
return build(1,n);
}
public List<TreeNode> build(int lo,int hi){
List<TreeNode> list = new ArrayList();
if(lo > hi){
list.add(null);
return list;
}
for(int i = lo;i <= hi;i++){//穷举 root 节点的所有可能
//递归构造出左右⼦树的所有合法 BST
List<TreeNode> left = build(lo,i - 1);
List<TreeNode> right = build(i + 1,hi);
//给 root 节点穷举所有左右⼦树的组合
for(TreeNode l : left){
for(TreeNode r : right){
TreeNode root = new TreeNode(i,l,r);
list.add(root);
}
}
}
return list;
}
}
96.不同的⼆叉搜索树
同95,只不过这里不需要返回节点,只需要个数。但是这种方法会超时
class Solution {
public int numTrees(int n) {
return build(1,n);
}
//返回lo-hi构造BST的种数
public int build(int lo,int hi){
if(lo > hi){
return 1;
}
int result = 0;
for(int i = lo;i <= hi;i++){
int lcount = build(lo,i - 1);
int rcount = build(i + 1,hi);
result += (lcount * rcount);
}
return result;
}
}
官方答案:动态规划:
因为G[i] += G[j - 1] * G[i - j];(其中j从1-i循环取值)
//动态规划:状态转移方程,初始化,循环设置
class Solution {
public int numTrees(int n) {
int[] result = new int[n + 1];
result[0] = 1;
result[1] = 1;
for(int i = 2;i <= n;i++){
for(int j = 1;j <= i;j++){/root从1-i,求左右子树组合的总和
result[i] += (result[j - 1] * result[i - j]);//result[j - 1]左子树个数,result[i - j]右子树个数
}
}
return result[n];
}
}
98.验证⼆叉搜索树
//二叉搜索树定义:并不是左节点<中<右节点,而是左子树所有节点<中节点<右子树所有节点
错误思路:
BST 左⼩右⼤的特性是指 root.val 要⽐左⼦树的所有节点都更⼤,要⽐右⼦树的所有 节点都⼩,只检查左右两个⼦节点当然是不够的。
//递归遍历所有节点,判断当前节点时,其左右子树当作是符合条件的。
//只需要比较root,root.left,root.right三个节点值。
class Solution {
public boolean isValidBST(TreeNode root) {
if(root == null) return true;
if(root.left != null && root.left.val >= root.val){
return false;
}
if(root.right != null && root.right.val <= root.val){
return false;
}
return isValidBST(root.left) && isValidBST(root.right);
}
}
//此方法错误在本题要求的是有效的二叉搜索树,要求的不止是左节点<中<右节点,而是左子树所有节点<中节点加粗样式<右子树所有节点
//[5,4,6,null,null,3,7]此例子该解法就错了。
正确的递归解法:增加函数参数列表,在参数中携带额外信息,将这种约束传递给⼦树的所有 节点
//1.递归,2.中序遍历,判断是否升序
class Solution {
public boolean isValidBST(TreeNode root) {
long lower = Long.MIN_VALUE,upper = Long.MAX_VALUE;
return isValid(root,lower,upper); // 要求root.val处于lower,upper之间
}
public boolean isValid(TreeNode root,long lower,long upper){
if(root == null) return true;
if(root.val <= lower || root.val >= upper){
return false;
}
return isValid(root.left,lower,root.val) && isValid(root.right,root.val,upper);
}
}
450.删除⼆叉搜索树中的节点
删除⽐插⼊和搜索都要复杂⼀些,分三种情况:
情况 1:A 恰好是末端节点,两个⼦节点都为空,那么它可以当场去世了:
为了不破坏 BST 的性质,A 必须找到左⼦树中最⼤的那个节点或者右⼦ 树中最⼩的那个节点来接替⾃⼰,
情况 2:A 若右子树不空,找出右子树最小的那个节点代替自己
情况 3:否则找到左⼦树中最⼤的那个节点来接替⾃⼰
// 情况 1:A 恰好是末端节点,两个⼦节点都为空,那么它可以当场去世了:
// 情况 2:A 若右子树不空,找出右子树最小的那个节点代替自己
// 情况 3:否则找到左⼦树中最⼤的那个节点来接替⾃⼰
class Solution {
public TreeNode deleteNode(TreeNode root, int key) {//找到对应key的节点
if(root == null) return root;
if(root.val == key){
root = delete(root);
}else if(root.val > key){
root.left = deleteNode(root.left,key);
}else{
root.right = deleteNode(root.right,key);
}
return root;
}
//删除传入树的key值根节点
public TreeNode delete(TreeNode root){
if(root.left == null && root.right == null){
root = null;
}else if(root.right != null){
root = rightmin(root);
root.right = deleteNode(root.right,root.val);
}else{
root = leftmax(root);
root.left = deleteNode(root.left,root.val);
}
return root;
}
public TreeNode rightmin(TreeNode root){
TreeNode temp = root.right;
while(temp.left != null){
temp = temp.left;
}
root.val = temp.val;
return root;
}
public TreeNode leftmax(TreeNode root){
TreeNode temp = root.left;
while(temp.right != null){
temp = temp.right;
}
root.val = temp.val;
return root;
}
}
// class Solution {
// public TreeNode deleteNode(TreeNode root, int key) {//找到对应key的节点
// if(root == null) return null;
// if(root.val == key){
// root = delete(root);
// }else if(root.val > key){
// root.left = deleteNode(root.left,key);
// }else{
// root.right = deleteNode(root.right,key);
// }
// return root;
// }
// //删除传入树的key值根节点
// public TreeNode delete(TreeNode root){
// if(root.left == null && root.right == null){
// root = null;
// }else if(root.right != null){
// root.val = rightmin(root);
// root.right = deleteNode(root.right,root.val);
// }else{
// root.val = leftmax(root);
// root.left = deleteNode(root.left,root.val);
// }
// return root;
// }
// public int rightmin(TreeNode root){
// root = root.right;
// while(root.left != null){
// root = root.left;
// }
// return root.val;
// }
// public int leftmax(TreeNode root){
// root = root.left;
// while(root.right != null){
// root = root.right;
// }
// return root.val;
// }
// }
700.⼆叉搜索树中的搜索
递归(二叉搜索树---二分)
class Solution {
public TreeNode searchBST(TreeNode root, int val) {
if(root == null) return null;
if(root.val > val){
return searchBST(root.left,val);
}else if(root.val < val){
return searchBST(root.right,val);
}else{
return root;
}
}
}
701.⼆叉搜索树中的插⼊操作
可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。
示例一的插入即每次都插入到叶子节点,如此,即可递归。
class Solution {
public TreeNode insertIntoBST(TreeNode root, int val) {
if(root == null) return new TreeNode(val);
if(root.val > val){
root.left = insertIntoBST(root.left,val);
}
if(root.val < val){
root.right = insertIntoBST(root.right,val);
}
return root;
}
}
补充:
遍历过程中对二叉树进行操作
public void midergodic(TreeNode root){
if(root == null) return;
midergodic(root.left);
//按顺序进行二叉树各节点操作
midergodic(root.right);
}
230.⼆叉搜索树中第 K ⼩的元素
二叉搜索树中序遍历获得集合升序集合list,get(k - 1)即可
class Solution {
public int kthSmallest(TreeNode root, int k) {
List<Integer> list = new ArrayList();
midergodic(root,list);
return list.get(k - 1);
}
public void midergodic(TreeNode root,List<Integer> list){
if(root == null) return;
midergodic(root.left,list);
list.add(root.val);
midergodic(root.right,list);
}
}
进阶:如果二叉搜索树经常被修改(插入/删除操作)并且你需要频繁地查找第 k 小的值
//仍然是中序遍历,但是中序遍历的第K个节点就是第k个元素,那么只需要定义一个count计数目前中序遍历多少个节点即可找到第k小节点
class Solution {
public int count;
public int res;
public int kthSmallest(TreeNode root, int k) {
midergodic(root,k);
return res;
}
public void midergodic(TreeNode root,int k){
if(root == null) return;
midergodic(root.left,k);
if(++count == k){
res = root.val;
return;
}
midergodic(root.right,k);
return;
}
}
538.把⼆叉搜索树转换为累加树
//正常中序是,左中右得升序,现在题目是求大于val的元素和,所以应该右中左,利用递归,pre为前几项之和,也是累加树的前一项值加原树的当前节点值
class Solution {
public int pre = 0;
public TreeNode convertBST(TreeNode root) {
midergodicrl(root);
return root;
}
public void midergodicrl(TreeNode root){
if(root == null) return;
midergodicrl(root.right);
root.val = root.val + pre;
pre = root.val;
midergodicrl(root.left);
}
}
1038.从二叉搜索树到更大和树
同538
class Solution {
public int pre = 0;
public TreeNode bstToGst(TreeNode root) {
midergodicrl(root);
return root;
}
public void midergodicrl(TreeNode root){
if(root == null) return;
midergodicrl(root.right);
root.val = root.val + pre;
pre = root.val;
midergodicrl(root.left);
}
}
501.⼆叉搜索树中的众数
//重复二叉搜索树:左<=中<=右
// BST 的中序遍历有序,在中序遍历的位置做⼀些判断逻辑和操作有序数组.
class Solution {
private int maxcount,nowcount,nowvalue;
public int[] findMode(TreeNode root) {
List<Integer> list = new ArrayList();
ergodic(root,list);
int[] nums = new int[list.size()];
for(int i = 0;i < list.size();i++){
nums[i] = list.get(i);
}
return nums;
}
public void ergodic(TreeNode root,List<Integer> list){
if(root == null) return;
ergodic(root.left,list);
update(root,list);
ergodic(root.right,list);
}
//对有序数组进行操作。
public void update(TreeNode root,List<Integer> list){
if(root.val == nowvalue){
//相等就进行计数+1
nowcount++;
}else{
//不相等就修改计数和当前值
nowcount = 1;
nowvalue = root.val;
}
//每遍历一个数都进行一次计数判断。
if(nowcount > maxcount){
list.clear();
list.add(nowvalue);
maxcount = nowcount;
}else if(nowcount == maxcount){
list.add(nowvalue);
}
}
}
进阶:不使用额外空间:即在二叉搜索树的中序递归遍历中进行值的判断等操作。
public void midergodic(TreeNode root){
if(root == null) return;
midergodic(root.left);
//按顺序进行二叉树各节点操作
midergodic(root.right);
}
class Solution {
private int maxcount = -1,nowcount = 0,prevalue = Integer.MAX_VALUE;
private List<Integer> list = new ArrayList();
public int[] findMode(TreeNode root) {
midergodic(root);
int[] nums = new int[list.size()];
for(int i = 0;i < list.size();i++){
nums[i] = list.get(i);
}
return nums;
}
public void midergodic(TreeNode root){
if(root == null) return;
midergodic(root.left);
//按顺序进行二叉树各节点操作
if(root.val == prevalue){
nowcount++;
}else{
nowcount = 1;
prevalue = root.val;
}
if(nowcount > maxcount){
list.clear();
list.add(root.val);
maxcount = nowcount;
}else if(nowcount == maxcount){
list.add(root.val);
}
midergodic(root.right);
}
}
530.⼆叉搜索树的最⼩绝对差
//1.中序遍历得list,然后遍历list用min记录最小差值
//2.不用额外空间,在递归遍历过程中按元素升序顺序进行操作
class Solution {
选择合适的预设值,解决第一个元素在递归中减去前一个值的边界问题
public int pre = -1000001;
public int res = Integer.MAX_VALUE;
public int getMinimumDifference(TreeNode root) {
midergodic(root);
return res;
}
public void midergodic(TreeNode root) {
if(root == null) return;
midergodic(root.left);
res = (res < root.val - pre ? res : root.val - pre);
pre = root.val;
midergodic(root.right);
}
}
783.⼆叉搜索树节点最⼩距离
同530.
1373.⼆叉搜索⼦树的最⼤键值和(困难题)
跳过,本以为
//思路1:遍历当前二叉树所有节点。每个节点判断是不是二叉搜索树,是的话计算值和记作公共变量max
//思路2:考虑二叉搜索树特性,中序遍历后为升序。那么考虑对二叉树进行中序遍历后,获得数组,
//本题求二叉搜索树的最大值和也就是求中序遍历所得数组中升序子数组的最大值和
//思路3:思路2扩展,不额外使用空间,在中序遍历中间进行升序子数组求和操作
思路3实现
class Solution {
public int maxsum = Integer.MIN_VALUE;
public int presum = 0;
public int pre = Integer.MAX_VALUE;
public int maxSumBST(TreeNode root) {
midergodic(root);
return maxsum;
}
public void midergodic(TreeNode root){
if(root == null) return;
midergodic(root.left);
if(root.val > pre){
presum += root.val;
}else{
presum = root.val;
}
pre = root.val;
maxsum = (presum > maxsum ? presum : maxsum);
midergodic(root.right);
}
}
但是好像不是这么简单,提交出错,因为如下,本算法会认为从节点1开始加上右子树就是最大的二叉搜索树,但是如此就是节点1没有左子树只有右子树就不算二叉搜索树。这种情况在上面的这种思路中无法排除。
所以就暴力解法吧,思路1即可。