文章:代码随想录
morris遍历:
其实理清楚morris遍历的过程就很好理解morris遍历了,
morris分为三个步骤:
对于每个节点,若无左树,则cur右移。
若有左树,则找到这个节点的前驱节点。就是左子节点的最右的一个节点。这个mostRight指针要遍历到这个前驱节点两次,第一次先把这个前驱节点的右指针从null指向cur,然后cur左移,继续重复遍历.第二次遍历到这个前驱节点,证明cur已经往上层返回了,把这个右指针再改回指向null.最后cur再右移动。
中序遍历代码:
//中序 左中右
//想想摩尔斯序是先把左子树都遍历了,然后会回到有左子树的中节点,这样会导致有左子树的节点会遍历两次.
//那么当cur需要往右移动的时候,打印是不是就是中序了呢?
//因为cur需要往右移动的情况有两种:
// 1.节点无左子树,那这时候没有左子树,左序为空,cur需要右移动,移动之前打印,是不是记录的就是中呢?
// 2.节点从左子树返回中节点。在遍历到左子树的叶子节点时,这个时候cur往右移动其实就是返回上一层的中节点,所以这个时候打印,打印的就是左序。
public void morrisIn(TreeNode root){
TreeNode cur=root;
TreeNode mostRight=null;
while(cur!=null){
mostRight=cur.left;
//有左子树
if(mostRight!=null){
//找到前驱节点
while(mostRight.right!=null && mostRight.right!=cur){
mostRight=mostRight.right;
}
//第一次遍历到前驱节点
if(mostRight.right==null){
//将右指针指向cur
mostRight.right=cur;
cur=cur.left;
continue;
}else {
//第二次遍历到,这时候要把指针改回来了。
mostRight.right=null;
}
}
//右移之前打印
System.out.println(cur.val);
//无左子树,右移
cur=cur.right;
}
}
二叉树中的众数,二叉搜索树的最小绝对差:
这两道题,都是利用搜索树的特性。中序遍历一定是有序数组,通过双指针的方式中序遍历树并更新变量或者集合的值,最后返回变量或者集合。知道这个特性,思路就很容易,基本都是两两比较。
这里我做的时候还没学morris,其实都可以用morris来做,先附上递归代码,后序二刷尝试用morris来解决.
二叉搜索树的最小绝对差代码:
//双指针遍历二叉搜索树
//中序遍历,因为已经时二叉搜索树了,所以中序遍历一定是升序无重复的,所以只要前后两两比较就能找到最小值.
TreeNode pre=null;
int min=Integer.MAX_VALUE;
public int getMinimumDifference(TreeNode root) {
if (root==null) return 0;
traversalIn(root);
return min;
}
private void traversalIn(TreeNode root) {
if(root==null) return;
traversalIn(root.left);
if(pre!=null){
//因为中序是升序的,所以不需要用绝对值方法
//给最小值每次重新赋值
min=Math.min(min,root.val- pre.val);
}
pre=root;
traversalIn(root.right);
}
二叉树中的众数代码:
public static int[] findMode(TreeNode root) {
if (root == null) return null;
traversalIn(root);
int[] result = new int[record.size()];
for (int i = 0; i < record.size(); i++) {
result[i] = record.get(i);
}
return result;
}
//这里为什么添加的是root.val而不是pre.val呢?
//其实添加pre.val的思路更好理解,count完之后再进行比较.
//但是尝试之后你会发现,写pre.val的话,开头和结束的临界条件判断很麻烦,不仅很多地方逻辑相同,冗余,还要加上特殊的判断条件,速度反而会更慢.
//比如pre为空的时候,就是第一个节点的时候,你需要直接加入这个元素,不然当树节点只有一个的时候会返回一个空结果集。
//比如null为空的时候,需要再判断一次count和maxCount,做添加或者清空操作,这里逻辑和后面的代码冗余,
// 然后同时root为空的时候并不代表遍历完全,如果父节点的右或者左还有和pre相同的连续节点,这样后面冗余的代码还会执行多次元素就会被重复添加,
// 那么就还得在这两处添加时加入contains判断,所以代码就很冗余复杂,速度会更慢.具体自实现代码在leeCode上提交了.
//所以还是比较cur指针要快很多,总结一下这种需要连续统计结果的,不是两两比较的,都统计cur会更好实现.
private static void traversalIn(TreeNode root) {
if (root == null) {
return;
}
traversalIn(root.left);
int rootValue = root.val;
// 计数
if (pre == null || rootValue != pre.val) {
count = 1;
} else {
count++;
}
// 更新结果以及maxCount
if (count > maxCount) {
record.clear();
record.add(rootValue);
maxCount = count;
} else if (count == maxCount) {
record.add(rootValue);
}
pre = root;
traversalIn(root.right);
}
//统一迭代法 栈
public int[] findModeStack(TreeNode root) {
int count = 0;
int maxCount = 0;
TreeNode pre = null;
LinkedList<Integer> res = new LinkedList<>();
Stack<TreeNode> stack = new Stack<>();
if(root != null)
stack.add(root);
while(!stack.isEmpty()){
TreeNode curr = stack.peek();
if(curr != null){
stack.pop();
if(curr.right != null)
stack.add(curr.right);
stack.add(curr);
stack.add(null);
if(curr.left != null)
stack.add(curr.left);
}else{
stack.pop();
TreeNode temp = stack.pop();
if(pre == null)
count = 1;
else if(pre != null && pre.val == temp.val)
count++;
else
count = 1;
pre = temp;
if(count == maxCount)
res.add(temp.val);
if(count > maxCount){
maxCount = count;
res.clear();
res.add(temp.val);
}
}
}
int[] result = new int[res.size()];
int i = 0;
for (int x : res){
result[i] = x;
i++;
}
return result;
}
二叉树的最近公共祖先
思路:
其实逻辑了解一次之后就比较简单。
//有两种情况:
//第一种情况两个节点在同一层同一个节点下,那么当前中节点就是最近公共祖先.
//第二种情况是两个节点不在同一层,但在同一边的子树,那么最先遍历到的目标节点本身就是最近公共祖先.
//你可能会有疑问?那如果不在同一层呢也不在同一边子树呢?那是不是根节点就是最近公共祖先?
//那如果在同一层,但是不在同一节点下呢?那是不是相对上面某一个父节点来说,这两个节点分别在它的左右子树?那这个节点是不是就是最近的公共祖先?
//其实你列举下来就会发现,其实过程就是找到目标结点之后,往上返回路径,找到两条路径的相遇节点。这样递归就很好理解了。
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 right;
else if(left!=null && right==null) return left;
//如果两边都是空,则都没找到则继续返回空
else return null;
}