226-翻转二叉树
将每个节点的左右节点进行交换就得到了翻转的二叉树
public static TreeNode reverseTreeDeep(TreeNode treeNode){
if(treeNode==null)return null;//递归的终止条件
//这里是放在了前序遍历的位置,也可以放在后序,放在中序遍历位置是不行的
TreeNode temp=treeNode.left;
treeNode.left=treeNode.right;
treeNode.right=temp;
reverseTreeDeep(treeNode.left);
//如果放在中序位置,其结果是4769213正常应该是4796231,可以看到我们的叶子节点没有翻转
//中序遍历按照左根右的顺序遍历,在遍历根的时候交换了左右节点,在遍历右节点的时候相当于又遍历了一遍之前的左节点
reverseTreeDeep(treeNode.right);
return treeNode;
}
116-填充二叉树节点的右侧指针
树结构
public class TreeNodeWithNext {
public int val;
public TreeNodeWithNext left;
public TreeNodeWithNext right;
public TreeNodeWithNext next;
public TreeNodeWithNext() { }
public TreeNodeWithNext(int val, TreeNodeWithNext left, TreeNodeWithNext right) {
this.val = val;
this.left = left;
this.right = right;
}
public TreeNodeWithNext(int val) {
this.val = val;
}
public void print(){
ArrayList<String> strings = new ArrayList<>();
strings.add(Integer.toString(this.val));
strings.add("#");
TreeNodeWithNext left=this.left;
while(left!=null){
strings.add(Integer.toString(left.val));
TreeNodeWithNext next=left.next;
while(next!=null){
strings.add(Integer.toString(next.val));
next=next.next;
}
strings.add("#");
left=left.left;
}
System.out.println(strings);
}
}
先来看一个比较巧妙的递归解法
public class likou116 {
public static TreeNodeWithNext connect(TreeNodeWithNext root){
dfs(root);
return root;
}
public static void dfs(TreeNodeWithNext root){
if(root==null)return;
TreeNodeWithNext left=root.left;
TreeNodeWithNext right=root.right;
//这里判断是left或者是right都可以因为是完美二叉树
while(left!=null){
left.next=right;
left=left.right;
right=right.left;
}
//递归调用左右节点
dfs(root.left);
dfs(root.right);
}
}
更加容易理解的一种递归算法如下
TreeNodeWithNext connect2(TreeNodeWithNext root){
if(root==null)return null;
easy(root.left,root.right);
return root;
}
public static void easy(TreeNodeWithNext node1,TreeNodeWithNext node2){
if(node1==null||node2==null)return;
node1.next=node2;//将传入的两个点连接
easy(node1.left,node1.right);
easy(node2.left,node2.right);
easy(node1.right,node2.left);
}
114--二叉树变单链表
public static void change(TreeNode root) {
if(root==null)return;
//叶子节点不需要操作
if(root.right==null&&root.left==null)return;
change(root.left);
change(root.right);
//后序遍历位置*************先来到树的最左下角
//记录两个子树
TreeNode left = root.left;
TreeNode right = root.right;
//将左子树作为右子树
root.left=null;
root.right=left;
//将原来的左子树接在右子树的末端(注意这里是末端)
TreeNode p=root;
while(p.right!=null){
p=p.right;
}
p.right=right;
}
654--最大二叉树
![](https://i-blog.csdnimg.cn/blog_migrate/cae897e1a3567e79d8badbe617e53737.png)
经典的递归问题,我之前的想法是每次都替换成新的数组,这样空间成本太大而且不容易处理边界,添加两个指针,只在一个数组上进行操作,这样高明很多.
public TreeNode constructMaximumBinaryTree(int[] nums) {
return build(nums,0,nums.length-1);
}
TreeNode build(int[] nums,int left,int right){
if(left>right)return null;
int index=-1,maxValue=Integer.MIN_VALUE;
//拿到最大值和最大值坐标
for (int i = left; i <= right; i++) {
if(maxValue<nums[i]){
index=i;
maxValue=nums[i];
}
}
TreeNode node=new TreeNode(maxValue);
node.left=build(nums,left,index-1);
node.right=build(nums,index+1,right);
return node;
}
105-从前序和中序遍历序列构造二叉树
要解决这道题需要了解前序遍历和中序遍历结果如何配合,前序遍历的第一个节点是根节点,在中序遍历中我们可以使用这个信息定位中序遍历中的根节点,中序遍历有一个技巧是,在根节点数列左边的必然是左子树,右边的必然是右子树,接下来只要具体切分递归就行了.
我们先通过中序遍历[9 3 15 20 7]获得到根节点3,其实只需要一个参数就可以完成切分,那就是我们左子树的数量num和根在中序遍历中的索引inorder_root就能完成也就是根节点的索引减去此次递归的左边界.
来看这次递归的左子树在先序遍历和中序遍历分别怎么切分.
- 对于中序遍历他的左子树边界为[left,inorder_root-1]---这里不包括根节点(-1操作)
- 对于前序遍历他的左子树他的边界为[left+1,left+num]--这里的左子树left+1也是为了排除根节点,然后右边界取决于左子树的节点个数,最后一个左子树节点索引为num+left
在来看右子树
- 对于中序遍历他的右子树边界为[inorder_root+1,right]
- 对于前序遍历的右子树边界为[left+num+1,right]---left+num的索引刚好到达最后一个左子树节点再+1就是右子树了.
至此所有递归的边界条件就分析完毕了
private Map<Integer, Integer> indexMap;
public TreeNode myBuildTree(int[] preorder, int[] inorder, int preorder_left, int preorder_right, int inorder_left, int inorder_right) {
if (preorder_left > preorder_right) {
return null;
}
// 前序遍历中的第一个节点就是根节点
int preorder_root = preorder_left;
// 在中序遍历中定位根节点
int inorder_root = indexMap.get(preorder[preorder_root]);
// 把根节点建立出来
TreeNode root = new TreeNode(preorder[preorder_root]);
// 得到左子树中的节点数目
int size_left_subtree = inorder_root - inorder_left;
// 递归地构造左子树,并连接到根节点
// 先序遍历中「从 左边界+1 开始的 size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素
root.left = myBuildTree(preorder, inorder, preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1);
// 递归地构造右子树,并连接到根节点
// 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素
root.right = myBuildTree(preorder, inorder, preorder_left + size_left_subtree + 1, preorder_right, inorder_root + 1, inorder_right);
return root;
}
public TreeNode buildTree(int[] preorder, int[] inorder) {
int n = preorder.length;
// 构造哈希映射,帮助我们快速定位根节点
indexMap = new HashMap<Integer, Integer>();
for (int i = 0; i < n; i++) {
indexMap.put(inorder[i], i);
}
return myBuildTree(preorder, inorder, 0, n - 1, 0, n - 1);
}
106--从中序和后序遍历构造二叉树
流程类似只是重新定义一遍边界,这里不再赘述
private HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
public TreeNode buildTree(int[] postorder, int[] inorder) {
for (int i = 0; i < inorder.length; i++) {
map.put(inorder[i], i);
}
return buildHelper(inorder, postorder, 0, inorder.length - 1, 0, postorder.length - 1);
}
private TreeNode buildHelper(int[] inOrder, int[] postOrder, int in_start, int in_end, int post_start, int post_end) {
if (post_start > post_end) {
return null;
}
int root_value = postOrder[post_end];
TreeNode root = new TreeNode(root_value);
Integer rootIndex = map.get(root_value);
Integer leftNum = rootIndex - in_start;
root.left=buildHelper(inOrder,postOrder,in_start,in_start+leftNum-1,post_start,post_start+leftNum-1);
root.right=buildHelper(inOrder,postOrder,in_start+leftNum+1,in_end,post_start+leftNum,post_end-1);
return root;
}
652--寻找重复子树
方法一:直接全部序列化,把每条路线的值全部放进count这个map里,再通过getOrDefault这个函数取操作他的value,在寻找value值为2的就说明是重复的字串
Map<String,Integer> count;
List<TreeNode> ans;
public List<TreeNode> findDupSub(TreeNode root){
count=new HashMap<>();
ans=new ArrayList<>();
collect(root);
return ans;
}
private String collect(TreeNode node) {
if(node==null)return "#";
//前序遍历
String serial=node.val+","+collect(node.left)+","+collect(node.right);
count.put(serial,count.getOrDefault(serial,0)+1);//存在serial这个字符串就返回正常值,不存在就返回0
if(count.get(serial)==2)ans.add(node);
return serial;
}
方法二是法一的进阶版,用了一个更简单的唯一标识符uid来代替本来冗长的string字符串,这里trees的目的仅仅是为了生成一个简单的唯一标识符
int t;
Map<String, Integer> trees;
Map<Integer, Integer> count2;
List<TreeNode> ans2;
public List<TreeNode> findDupSub2(TreeNode root) {
t = 1;
count2 = new HashMap<>();
ans2 = new ArrayList<>();
trees = new HashMap<>();
lookup(root);
return ans2;
}
private int lookup(TreeNode node) {
if (node == null) return 0;
String serial = node.val + "," + lookup(node.left) + "," + lookup(node.right);
//如果不存在的化就进行计算,第二个参数是函数式接口
int uid = trees.computeIfAbsent(serial, x -> t++);//相当于给了每个节点一个唯一id,uid是一个很短的数,计算起来也更加方便
count2.put(uid, count2.getOrDefault(uid, 0) + 1);
if (count2.get(uid) == 2) ans2.add(node);
return uid;
}
likou230--二叉搜索树中第k小的元素
二叉搜索树有一个重要的规律,其中序遍历是升序的,收集起来第k小的就是索引为k-1的元素
递归方法:
private ArrayList<Integer> collect;
public int kthSmallest(TreeNode root, int k) {
collect=new ArrayList<>();
ArrayList<Integer> list = find(root, collect);
return list.get(k-1);
}
private ArrayList<Integer> find(TreeNode root, ArrayList<Integer> collect) {
//使用中序遍历收集元素
if(root==null)return collect;
find(root.left, collect);
collect.add(root.val);
find(root.right, collect);
return collect;
}
迭代方法:
private Stack<TreeNode> stack;
public int kthSmall(TreeNode root, int k) {
stack=new Stack<>();
while (true){
//移动到最左下角
while (root!=null){
stack.add(root);
root=root.left;
}
//root等于弹出的节点
root = stack.pop();
if(--k==0)return root.val;//如果k等于0了直接抛出
root=root.right;//如果k不等于0就往右节点走,如果右节点为null会继续抛出节点
}
}
538/1038--二叉搜索树变累加树
使用反向的中序遍历,通过右根左的顺序就能从最大的8开始遍历数据了
public class likou538 {
int sum;
public TreeNode convertBST(TreeNode root) {
if(root!=null){
convertBST(root.right);
sum+=root.val;
root.val=sum;
convertBST(root.left);
}
return root;
}
}
450-删除二叉搜索树的某一个节点
删除一个节点有三种情况:
情况1:要删除的节点恰好是末端节点,两个子节点都为空,直接删就好了
情况2:要删除的节点有一个子节点,让这个子节点接替自己的位置
情况3:要删除的节点有两个子节点,必须找到左子树中最大的节点或者右子树中最小的那个节点来接替自己
//寻找右子树中最小的节点
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;
}
public TreeNode deleteNode(TreeNode root,int key){
if(root==null)return null;//递归终止条件
if(key>root.val){//查找的节点比根节点大
root.right=deleteNode(root.right,key);
}else if(key<root.val) {//查找的节点比根节点小
root.left=deleteNode(root.left,key);
}else{//找到了根节点
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;
}
701--二叉搜索树中的插入操作
public TreeNode insertIntoBST(TreeNode root, int val) {
if(root==null)return new TreeNode(val);
if(val>root.val)root.right=insertIntoBST(root.right,val);
else root.left=insertIntoBST(root.left,val);
return root;
}
700--二叉树中的搜索
public class likou700 {
public TreeNode searchBST(TreeNode root, int val) {
if(root==null||root.val==val)return root;
return root.val>val?searchBST(root.left,val):searchBST(root.right,val);
}
}
98--判断是不是二叉搜索树
对于一个非二叉搜索树来看他的打印规则
public class likou98 {
public static void main(String[] args) {
TreeNode treeNode = new TreeNode(5, new TreeNode(1), new TreeNode(4, new TreeNode(3), new TreeNode(6)));
likou98 likou98 = new likou98();
System.out.println(likou98.isValidBST(treeNode));
}
long pre=Long.MIN_VALUE;
public boolean isValidBST(TreeNode root) {
if(root==null){
System.out.println("root为null了");
return true;}
if(!isValidBST(root.left)){
System.out.println("进来了root.left");
return false;
}
//运行到这里的时候代码已经到达了最左下角(最小的值)
//访问当前节点,判断是否大于前一个节点
if(root.val<=pre){
System.out.println("小于前方节点");
return false;
}
System.out.println("遍历右子树");
pre=root.val;
return isValidBST(root.right);
}
}
过程分析:
1.进入节点5的方法,不为null,往if条件的方法走,遍历其左节点
2.进入节点1的方法,不为null,继续往左节点走
3.进入null节点,是空的返回true,并且打印root为null了
4.回到节点1的方法,.返回的是true,因此不进入左节点的判断,继续往下走,当前值1大于pre值不进入小于前方节点的if,往下走,打印遍历右子树,来到其右子树的方法
5.其右子树为null,打印root为null了,直接返回true,回来节点1的方法,节点1方法运行完成返回true;
5.回到节点5的方法,往下走,pre=1<当前值5,不进入小于前方节点,打印遍历右子树,进入其右节点4的方法
6.右节点4不为null,一直往左走,进入3的方法后,进入3的左子树null的方法,null方法返回true,打印root为null了
7.进入3的方法,当前值为3 pre值为5 3<5于是进入3方法的小于前方节点,返回false
8.回到4的方法,3方法返回来是false,4方法进入进来了root.left,返回false
9.回到5的方法,由于4方法返回的是false,它也就return false
root为null了
遍历右子树
root为null了
遍历右子树
root为null了
小于前方节点
进来了root.left
false
该算法的精巧地方在于,只要有一个节点return了false,那么最终的结果就会是false,而且当一个节点return了false之后它会直接返回主节点,不会去查看剩下的节点.
来一个更好理解的算法,虽然效率稍低,运用中序遍历是按照升序的规则,这道题就很好解答了
private ArrayList<Integer> arrayList;
public boolean isValid(TreeNode root){
if(root==null)return true;
arrayList=new ArrayList<>();
collect(root);
for (int i = 1; i < arrayList.size(); i++) {
if(arrayList.get(i)<=arrayList.get(i-1))return false;
}
return true;
}
public void collect(TreeNode root){
if(root!=null){
collect(root.left);
arrayList.add(root.val);
collect(root.right);
}
}
likou96--判断有多少种不同的二叉搜索树
int [][]memo;
int numTrees(int n){
memo=new int[n+1][n+1];//初始化备忘录,全部为0,待会存数据从1开始存所以初始化容量为n+1
return count(1,n);
}
private int count(int left, int right) {
if(left>right)return 1;//空区间的时候,对应着空节点null,但也是一种情况,所以要返回1
if(memo[left][right]!=0)return memo[left][right];
int res=0;
for (int mid = left; mid <=right ; mid++) {
int leftNum=count(left,mid-1);//根节点的左子树有几种情况
int rightNum=count(mid+1,right);//根节点的右子树有几种情况
res+=leftNum*rightNum;
}
//把结果存进备忘录
memo[left][right]=res;
return res;
}
likou95--穷举所有二叉搜索树的组合
所以如果求 1...n 的所有可能。
我们只需要把 1 作为根节点,[ ] 空作为左子树,[ 2 ... n ] 的所有可能作为右子树。
2 作为根节点,[ 1 ] 作为左子树,[ 3...n ] 的所有可能作为右子树。
3 作为根节点,[ 1 2 ] 的所有可能作为左子树,[ 4 ... n ] 的所有可能作为右子树,然后左子树和右子树两两组合。
4 作为根节点,[ 1 2 3 ] 的所有可能作为左子树,[ 5 ... n ] 的所有可能作为右子树,然后左子树和右子树两两组合。
...
n 作为根节点,[ 1... n ] 的所有可能作为左子树,[ ] 作为右子树。
至于,[ 2 ... n ] 的所有可能以及 [ 4 ... n ] 以及其他情况的所有可能,可以利用上边的方法,把每个数字作为根节点,然后把所有可能的左子树和右子树组合起来即可。
public List<TreeNode> generateTrees(int n) {
List<TreeNode> ans = new ArrayList<TreeNode>();
if (n == 0) {
return ans;
}
return getAns(1, n);
}
private List<TreeNode> getAns(int start, int end) {
List<TreeNode> ans = new ArrayList<TreeNode>();
//此时没有数字,将 null 加入结果中
if (start > end) {
ans.add(null);
return ans;
}
//只有一个数字,当前数字作为一棵树加入结果中
if (start == end) {
TreeNode tree = new TreeNode(start);
ans.add(tree);
return ans;
}
//尝试每个数字作为根节点
for (int i = start; i <= end; i++) {
//得到所有可能的左子树
List<TreeNode> leftTrees = getAns(start, i - 1);
//得到所有可能的右子树
List<TreeNode> rightTrees = getAns(i + 1, end);
//左子树右子树两两组合
for (TreeNode leftTree : leftTrees) {
for (TreeNode rightTree : rightTrees) {
TreeNode root = new TreeNode(i);
root.left = leftTree;
root.right = rightTree;
//加入到最终结果中
ans.add(root);
}
}
}
return ans;
}
关于其他二叉搜索树的操作
//是否是相同的bst树
public boolean isSameTree(TreeNode p, TreeNode q) {
if (p == null && q == null) return true;
else if (p == null || q == null) return false;
if (p.val != q.val) return false;
return isSameTree(p.right, q.right) && isSameTree(p.left, q.left);
}
//是否是合理的bst 由于bst要求所有的节点都比右子树的节点小,所以添加两个节点min和max用于比较
boolean isValidBST(TreeNode node) {
return isValidBST(node, null, null);
}
boolean isValidBST(TreeNode node, TreeNode min, TreeNode max) {
if (node == null) return true;
if (min != null && node.val <= min.val) return false;
if (max != null && node.val >= max.val) return false;
//比较左边的节点是否比当前最小节点要小并且比最小节点要大
return isValidBST(node.left, min, node) && isValidBST(node.right, node, max);
}
//通过BST查找一个数是否存在
boolean isInBST(TreeNode root, int target) {
if (root == null) return false;
if (root.val == target) return true;
if (root.val > target) return isInBST(root.left, target);
else return isInBST(root.right, target);
}
//通过BST插入一个数
TreeNode insertIntoBST(TreeNode root, int val) {
if (root == null) return new TreeNode(val);//找到空位置插入新的节点
//BST中一般不会插入已经存在的元素
if (root.val < val) root.right = insertIntoBST(root.right, val);
if (root.val > val) root.left = insertIntoBST(root.left, val);
return root;
}
//在BST删除一个数
TreeNode deleteNode(TreeNode root, int key) {
if (root == null) return null;
if (root.val == key) {
//恰好是末端节点,两个子节点都为空,直接删除该节点 或者说只有一个非空子节点,那么使用它直接代替自己的位置
if (root.left == null) return root.right;
if (root.right == null) return root.left;
//如果当前节点有左子树和右子树
TreeNode minNode=getMin(root.right);
root.val=minNode.val;//交换节点
//删除已经替换掉值的节点
root.right=deleteNode(root.right,minNode.val);
}else if(root.val>key)root.left=deleteNode(root.left,key);
else root.right=deleteNode(root.right,key);
return root;
}
//获得左子树中最大的那个节点或者右子树最小的那个节点来代替自己(这里使用右子树最小的那个节点)
TreeNode getMin(TreeNode node){
while (node.left!=null) node=node.left;
return node;
}
likou297--二叉树的序列化和反序列化
本来还原一个二叉树我们必须拥有前中遍历结果和中后遍历结果,但是如果我们知道空指针信息的化,就可以只需要一种遍历结果就能推出二叉树
// Encodes a tree to a single string.
public String serialize(TreeNode root) {
return mySerialize(root,"");
}
private String mySerialize(TreeNode root, String s) {
if(root==null)return s+="n,";
s +=root.val+",";
s=mySerialize(root.left,s);
s=mySerialize(root.right,s);
return s;
}
// Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
String[] split = data.split(",");
ArrayList<String> dataList = new ArrayList<>(Arrays.asList(split));
return myDeserialize(dataList);
}
private TreeNode myDeserialize(ArrayList<String> dataList) {
//前序遍历位置
if(dataList.get(0).equals("n")){
dataList.remove(0);
return null;
}
TreeNode node = new TreeNode(Integer.parseInt(dataList.get(0)));
dataList.remove(0);
node.left=myDeserialize(dataList);
node.right=myDeserialize(dataList);
return node;
}
likou222--完全二叉树的节点个数
如果是一颗普通的二叉树,只需要按照如下方式遍历即可
public int countNodes(TreeNode root) {
if(root==null)return 0;
return 1+countNodes(root.left)+countNodes(root.right);
}
如果是一颗满二叉树,他的节点数为2的树高度次方-1
int h = 0;
// 计算树的高度
while (root != null) {
root = root.left;
h++;
}
// 节点总数就是 2^h - 1
return (int)Math.pow(2, h) - 1;
如果是一颗完全二叉树,我们可以先遍历左子树和右子树的高度,如果高度相同,说明就是一颗满二叉树,那么我们直接返回满二叉树的结果,如果不同就按照普通二叉树进行计算,其巧妙的地方在于最后的两个递归只有一个会递归下去,因为一颗完全二叉树的左右子树至少有一颗是满二叉树
public int countNodes(TreeNode root) {
TreeNode l = root, r = root;
// 记录左、右子树的高度
int hl = 0, hr = 0;
while (l != null) {
l = l.left;
hl++;
}
while (r != null) {
r = r.right;
hr++;
}
// 如果左右子树的高度相同,则是一棵满二叉树
if (hl == hr) {
return (int)Math.pow(2, hl) - 1;
}
// 如果左右高度不同,则按照普通二叉树的逻辑计算
return 1 + countNodes(root.left) + countNodes(root.right);
}