前序遍历
public TreeNode pruneTree (TreeNode root) {
//前序
doSomeThing();
root.left =pruneTree(root.left);
root.right= pruneTree(root.right);
}
中序遍历
public TreeNode pruneTree (TreeNode root) {
root.left =pruneTree(root.left);
//中序
doSomeThing();
root.right= pruneTree(root.right);
}
后续遍历
public TreeNode pruneTree (TreeNode root) {
root.left =pruneTree(root.left);
root.right= pruneTree(root.right);
//后续
doSomeThing();
}
剪枝 一般配合dfs
假设有一棵树,最上层的是root节点,而父节点会依赖子节点。
如果现在有一些节点已经标记为无效,我们要删除这些无效节点。
如果无效节点的依赖的节点还有效,那么不应该删除,如果无效节点和它的子节点都无效,则可以删除。
剪掉这些节点的过程,称为剪枝,目的是用来处理二叉树模型中的依赖问题。
回溯算法会应用「剪枝」技巧达到以加快搜索速度。有些时候,需要做一些预处理工作(例如排序)才能达到剪枝的目的。预处理工作虽然也消耗时间,但能够剪枝节约的时间更多
23、二叉搜索树的后序遍历
class Solution {
public boolean verifyPostorder(int[] postorder) {
return recur(postorder, 0, postorder.length - 1);
}
boolean recur(int[] postorder, int i, int j) {
if(i >= j) return true;
int p = i;
while(postorder[p] < postorder[j]) p++;
int m = p;
while(postorder[p] > postorder[j]) p++;
return p == j && recur(postorder, i, m - 1) && recur(postorder, m, j - 1);
}
}
这种做法非常漂亮 采用这个
class Solution {
// 要点:二叉搜索树中根节点的值大于左子树中的任何一个节点的值,小于右子树中任何一个节点的值,子树也是
public boolean verifyPostorder(int[] postorder) {
//if (postorder.length < 2) return true;
if (postorder.length == 1) return true;//只有一个根节点 是二叉搜索树
if (postorder.length == 0) return false;//空的 不是二叉搜索树
return verify(postorder, 0, postorder.length - 1);
}
// 递归实现
private boolean verify(int[] postorder, int left, int right){
if (left >= right) return true; // 当前区域不合法的时候直接返回true就好
int rootValue = postorder[right]; // 当前树的根节点的值
int k = left;
while (k < right && postorder[k] < rootValue){ // 从当前区域找到第一个大于根节点的,说明后续区域数值都在右子树中
k++;
}
for (int i = k; i < right; i++){ // 进行判断后续的区域是否所有的值都是大于当前的根节点,如果出现小于的值就直接返回false
if (postorder[i] < rootValue) return false;
}
// 当前树没问题就检查左右子树
if (!verify(postorder, left, k - 1)) return false; // 检查左子树
if (!verify(postorder, k, right - 1)) return false; // 检查右子树
return true; // 最终都没问题就返回true
}
}
24、二叉搜索树中和为某一值的路径
回溯就是暴力试探法+不满足回溯法,树的前序遍历就是试探,树的后序遍历就是回溯
递归过程必定会产生回溯
class Solution {
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;
}
void recur(TreeNode root, int tar) {
if(root == null) return;
path.add(root.val);//根节点添加到path中
tar -= root.val;//目标值逐渐减小
if(tar == 0 && root.left == null && root.right == null)
res.add(new LinkedList(path));//将此路径 path 加入 res 。
//因为 path 是对象类型(区别于基本数据类型),res.add(path) 的意思是,把 path 的「地址」添加到 res 中,path 在深度优先遍历以后为空列表,所以您最后会看到 res 中是一个一个的空列表,它们都指向一块内存。
//new ArrayList<>() 的作用是复制,把遍历到叶子结点的时候 path 的样子复制出来,到一个新的列表,这样写得话,最后 res 里保存的就是不同的变量了
recur(root.left, tar); //左节点遍历
recur(root.right, tar);//右节点遍历
path.removeLast();//回溯时,将当前路径从节点中删除 这一步是执行完一条路径后才执行的
}
}
26、二叉搜索树与双向链表
二叉搜索树的中序遍历为 递增序列
class Solution {
Node head, pre;
public Node treeToDoublyList(Node root) {
if(root==null) return null;
dfs(root);
pre.right = head;
head.left =pre;//进行头节点和尾节点的相互指向,这两句的顺序也是可以颠倒的
return head;
}
public void dfs(Node cur){
if(cur==null) return;
dfs(cur.left);
//pre用于记录双向链表中位于cur左侧的节点,即上一次迭代中的cur,当pre==null时,cur左侧没有节点,即此时cur为双向链表中的头节点
if(pre==null) head = cur;
//反之,pre!=null时,cur左侧存在节点pre,需要进行pre.right=cur的操作。
else pre.right = cur;
cur.left = pre;//pre是否为null对这句没有影响,且这句放在上面两句if else之前也是可以的。
pre = cur;//pre指向当前的cur,修改双向节点引用
dfs(cur.right);//全部迭代完成后,pre指向双向链表中的尾节点,递归右子树
}
}
先遍历右子树 再根节点 再左子树 得到的就是升序链表
在正常的中序遍历完成时,pre是最右边/最大值的结点;也就是正序的表尾; 而系统又把pre当作正序表头,这样得到的双向链表pre先从左往右走,只有自己;再从右往左走,符合。 所以我们要么 按中序的反序 右-左-根;要么得到pre之后,再找到正序的表头(存下来)
public class Solution {
TreeNode pre=null;
public TreeNode Convert(TreeNode pRootOfTree) {
if (pRootOfTree==null)
return null;
Convert(pRootOfTree.right);
if (pre!= null){
pRootOfTree.right=pre;
pre.left=pRootOfTree;
}
pre=pRootOfTree;
Convert(pRootOfTree.left);
return pre;
}
}
68、二叉搜索树的最近公共祖先
class Solution {
public TreeNode lowestCommonAncestor(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;
}
return root;
}
}
递归
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;
}
}
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null) return null; // 如果树为空,直接返回null
if(root == p || root == q) return root; // 如果 p和q中有等于 root的,那么它们的最近公共祖先即为root(一个节点也可以是它自己的祖先)
TreeNode left = lowestCommonAncestor(root.left, p, q); // 递归遍历左子树,只要在左子树中找到了p或q,则先找到谁就返回谁
TreeNode right = lowestCommonAncestor(root.right, p, q); // 递归遍历右子树,只要在右子树中找到了p或q,则先找到谁就返回谁
if(left == null) return right; // 如果在左子树中 p和 q都找不到,则 p和 q一定都在右子树中,右子树中先遍历到的那个就是最近公共祖先(一个节点也可以是它自己的祖先)
else if(right == null) return left; // 否则,如果 left不为空,在左子树中有找到节点(p或q),这时候要再判断一下右子树中的情况,如果在右子树中,p和q都找不到,则 p和q一定都在左子树中,左子树中先遍历到的那个就是最近公共祖先(一个节点也可以是它自己的祖先)
else return root; //否则,当 left和 right均不为空时,说明 p、q节点分别在 root异侧, 最近公共祖先即为 root
}
}
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null)
return null;
if(p == root || q == root)
return root;
TreeNode left = lowestCommonAncestor(root.left,p,q);
TreeNode right = lowestCommonAncestor(root.right,p,q);
if(left != null && right != null)
return root;
return left == null ? right : left;
}
}