今天写写《剑指offer》中树的解题思路
文章目录
07.重建二叉树
题目:输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
思路:前序遍历[根左右],中序遍历[左根右],用hashmap存储中序遍历的引索,用递归来建立树
public TreeNode buildTree(int[] preorder, int[] inorder) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < inorder.length; i++) {
map.put(inorder[i], i);
}
int len = preorder.length - 1;
return rebuild(preorder, 0, len, 0, len, map);
}
public TreeNode rebuild(int[] preorder, int preL, int preR, int inL, int inR, Map<Integer, Integer> map) {
if (preL > preR) {
return null;
}
int rootVal = preorder[preL];
int inRoot = map.get(rootVal);
TreeNode root = new TreeNode(rootVal);
root.left = rebuild(preorder, preL + 1, preL + inRoot - inL, inL, inRoot - 1, map);
root.right = rebuild(preorder, preL + 1 + inRoot - inL, preR, inRoot + 1, inR, map);
return root;
}
26.树的子结构
很简单,递归,要么B是A树的子结构,或者是A的左子树的子结构,或者A的右子树的子结构
public boolean isSubStructure(TreeNode A, TreeNode B) {
if(A==null||B==null) return false;
return recur(A,B)||isSubStructure(A.left,B)||isSubStructure(A.right,B);
}
public boolean recur(TreeNode A, TreeNode B) {
if(B==null){
return true;
}
if(A==null||B==null){
return false;
}
if(A.val!=B.val){
return false;
}
return recur(A.left,B.left)&&recur(A.right,B.right);
}
27.二叉树的镜像
递归写法
递归写法
public TreeNode mirrorTree(TreeNode root) {
if(root == null) return null;
TreeNode temp = root.left;
root.left = mirrorTree(root.right);
root.right = mirrorTree(temp);
return root;
}
BFS写法:加入队列前交换
public TreeNode mirrorTree(TreeNode root) {
if(root == null) return root;
Queue<TreeNode> queue=new LinkedList<>();
queue.add(root);
while(!queue.isEmpty()){
int sz=queue.size();
for(int i=0;i<sz;i++){
TreeNode cur=queue.poll();
TreeNode temp=cur.left;
cur.left=cur.right;
cur.right=temp;
if(cur.left!=null){
queue.offer(cur.left);
}
if(cur.right!=null){
queue.offer(cur.right);
}
}
}
return root;
}
32.Z型从上到下打印二叉树
BFS加双向队列Deque就信了:奇数行removeFirst,addLast;偶数行removeLast,addFirst;用deque.isEmpty()来判断是否要继续BFS
public List<List<Integer>> levelOrder(TreeNode root) {
Deque<TreeNode> deque = new LinkedList<>();
List<List<Integer>> res = new ArrayList<>();
if(root != null) deque.add(root);
while(!deque.isEmpty()){
//先是奇数行
List<Integer> temp = new ArrayList<>();
for(int i = deque.size(); i > 0;i--){
TreeNode node = deque.removeFirst();
temp.add(node.val);
if(node.left != null) deque.addLast(node.left);
if(node.right != null) deque.addLast(node.right);//从后面加
}
res.add(temp);
if(deque.isEmpty()){
break;
}
//偶数行
temp = new ArrayList<>();
for(int i = deque.size(); i > 0;i--){
TreeNode node = deque.removeLast();
temp.add(node.val);
if(node.right != null) deque.addFirst(node.right);
if(node.left != null) deque.addFirst(node.left);
}
res.add(temp);
}
return res;
}
34.二叉树中和为某一值的路径(重点)
法一:回溯:
- 回溯强调在状态空间大的时候,可以一份状态变量去搜索所有的可能的状态;
- 在搜索到符合条件的解的时候,通常会做一个拷贝,经常在递归终止条件的时候,有 res.add(new ArrayList<>(path));
- 正是因为全程使用一份状态变量,因此它就有恢复现场(return)和撤销选择(remove最后一个节点值)的需要。
public List<List<Integer>> pathSum(TreeNode root, int sum) {
List<List<Integer>> res=new ArrayList<>();
if(root==null) return res;
List<Integer> path=new ArrayList<>();//一份变量
dfs(root,sum,path,res);
return res;
}
public void dfs(TreeNode root,int sum,List<Integer> path,List<List<Integer>> res){
//叶子节点就是base case咯
if(root.left==null&&root.right==null){
if(root.val==sum){
path.add(root.val);
res.add(new ArrayList<Integer>(path));
path.remove(path.size()-1);//撤销选择
}
return;//恢复现场
}
if(root.left==null){
path.add(root.val);
dfs(root.right,sum-root.val,path,res);
path.remove(path.size()-1);
return;
}
if(root.right==null){
path.add(root.val);
dfs(root.left,sum-root.val,path,res);
path.remove(path.size()-1);
return;
}
path.add(root.val);
dfs(root.left,sum-root.val,path,res);
path.remove(path.size()-1);
path.add(root.val);
dfs(root.right,sum-root.val,path,res);
path.remove(path.size()-1);
}
法二:回溯
LinkedList<List<Integer>> res = new LinkedList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> pathSum(TreeNode root, int sum) {
recur(root,sum);
return res;
}
public void recur(TreeNode root,int tar){
if(root==null) return;
path.add(root.val);
tar -= root.val;
//先序遍历
if(tar==0 && root.left==null && root.right==null){
res.add(new LinkedList(path));//一定要new一个
}
recur(root.left,tar);
recur(root.right, tar);
path.removeLast();//记得回溯
}
37. 序列化二叉树
困难题,但是结合BFS就是处理一些细节上的问题了,主要是对null处理
- 因为要序列化null,所以BFS提交队列的时候不判断是否为null,在弹出队列的时候判断即可;
- 反序列化的时候,遇见null(说明poll的节点是叶子)什么也不做,更新引索
public String serialize(TreeNode root) {
if(root==null) return "[]";
StringBuilder res=new StringBuilder("[");
Queue<TreeNode> queue=new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()){
int sz=queue.size();
for(int i=0;i<sz;i++){
TreeNode cur=queue.poll();
if(cur!=null){
res.append(cur.val+",");
queue.offer(cur.left);
queue.offer(cur.right);
}else{
res.append("null,");
}
}
}
res.deleteCharAt(res.length()-1);
res.append("]");
return res.toString();
}
public TreeNode deserialize(String data) {
if(data.equals("[]")) return null;
String[] vals=data.substring(1,data.length()-1).split(",");
TreeNode root=new TreeNode(Integer.parseInt(vals[0]));
Queue<TreeNode> queue=new LinkedList<>();
queue.add(root);
int i=1;
while (!queue.isEmpty()){
TreeNode cur=queue.poll();
//vals[i]==null的话就不更新左右子节点
if(!vals[i].equals("null")){
TreeNode node=new TreeNode(Integer.parseInt(vals[i]));
cur.left=node;
queue.offer(node);
}
i++;
if(!vals[i].equals("null")){
TreeNode node=new TreeNode(Integer.parseInt(vals[i]));
cur.right=node;
queue.offer(node);
}
i++;
}
return root;
}
54. 二叉搜索树的第k大节点(经典)
第一次做完全不觉得是简单难度的,思路:全局变量+中序遍历倒序+剪枝
int res,k;
public int kthLargest(TreeNode root, int k) {
this.k=k;
dfs(root);
return res;
}
void dfs(TreeNode root){
if(root==null) return;
dfs(root.right);//中序遍历倒序,先right后left
if(k==0) return;//剪枝,在k=0后,无需遍历,提前返回
if(--k==0) res=root.val;//当更新k为0的时候,得到res,在下一轮遍历中return
dfs(root.left);
}
55 - II. 平衡二叉树(经典)
最佳解法:后序遍历(时间空间都是线性的)+剪枝(一旦某个节点的左右子树的高度相差大于1,立即返回)
public boolean isBalanced(TreeNode root) {
if(dfs(root)!=-1) return true;
return false;
}
private int dfs(TreeNode root){
if(root==null) return 0;
int left=dfs(root.left);
if(left==-1) return -1;
int right=dfs(root.right);
if(right==-1) return -1;
return Math.abs(left-right)<2?Math.max(left,right)+1:-1;
}
68 - I. 二叉搜索树的最近公共祖先
p,q的val都大于root的,在root的右子树
p,q的val都小于root的,在root的左子树
否则就是root
迭代法
public TreeNode lowestCommonAncestor_v2(TreeNode root, TreeNode p, TreeNode q) {
while (root != null) {
if (root.val < p.val && root.val < q.val) // p,q 都在 root 的右子树中
root = root.right; // 遍历至右子节点
else if (root.val > p.val && root.val > q.val) // p,q 都在 root 的左子树中
root = root.left; // 遍历至左子节点
else break;//否则就是root
}
return root;
}
递归法
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null) return null;
if (p.val < root.val && q.val < root.val) return lowestCommonAncestor(root.left, p, q);
if (p.val > root.val && q.val > root.val) return lowestCommonAncestor(root.right, p, q);
return root;
}
68 - II. 二叉树的最近公共祖先(重点)
假设设root是p和q的公共祖先,只有以下三种情况:
- p和q在root的子树中,在root的不同侧
- p=root,q在root的左右子树中
- q=root,p在root的左右子树中
考虑通过递归对二叉树进行后序遍历,当遇到节点 p 或 q 时返回。从底至顶回溯,当节点 p, q 在节点 root 的异侧时,节点 root 即为最近公共祖先,则向上返回 root 。
public TreeNode lowestCommonAncestor_2(TreeNode root, TreeNode p, TreeNode q) {
//后序遍历
if (root == null || root == p || root == q) return root;
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
if (left == null) return right;
if (right == null) return left;
return root;//left right同时不为空,说明root是最近公共祖先
}