二叉搜索树的最小绝对差
题目详细:LeetCode.530
这道题使我第一次了解到二叉树的双指针遍历法,详细可以先查看卡哥的讲解视频:《代码随想录 — 二叉搜索树中的众数》
利用二叉搜索树的特点:
- 中序遍历二叉搜索树得到一个有序序列
- 计算序列中相邻的每一个数的差值,记录最小绝对值差
但我们可以发现,如果我们可以在遍历过程中去计算相邻的两个数的差值,那么速度将提升很多,对于序列是否有序这一点似乎并不是特别重要,只是二叉搜索树赋予了它这一特点。
那么我们可以利用双指针法:
- 定义一个指针 pre 指向当前节点的前一个节点
- 按照左中右的顺序,深度优先遍历树的节点
- 若 pre 为空,则说明当前节点为叶子节点,不存在前一个节点
- 若 pre 不为空,则计算前一个节点与当前节点的绝对差值,利用全局变量记录一个最小值结果
- 注意更新前一个节点 pre = cur
Java解法(递归):
class Solution {public int ans = Integer.MAX_VALUE;public TreeNode pre = null;public int getMinimumDifference(TreeNode root) {this.dfs(root);return ans;}public void dfs(TreeNode cur){if(null == cur) return;this.dfs(cur.left);if(null != this.pre){ans = Math.min(ans, Math.abs(pre.val - cur.val));}this.pre = cur;this.dfs(cur.right);}
}
二叉搜索树中的众数
题目详细:LeetCode.501
传统的暴力解法有:
- 解法一:得到中序遍历序列后统计序列中出现次数最多的数字
- 解法二:遍历一次二叉树记录最高出现频率,再中序遍历一次二叉树将出现频率等于最高出现频率的数字加入结果集
- 这种方法都相当于需要遍历两次二叉树,效率较低,这里就不多赘述了
那么我们能否利用二叉搜索树中序遍历有序的特点,在遍历过程中就统计最高出现频率的数字呢?
在经过上一题了解二叉搜索树中双指针法的应用后,在遇到二叉搜索树相关问题时就又多了一种解题思路(中序遍历+双指针法):
- 定义两个变量,times 记录当前数字的出现频率,max_times 记录最高出现频率
- 众所周知,利用二叉搜索树的特点通过中序遍历可以得到一个有序序列
- 因为中序遍历结果是有序序列,所以数字一定是递增地连续地出现的,那么利用双指针法:
- 在递归中序遍历二叉树的过程中,通过比较 pre 和 cur 的数值,记录当前数字的出现频率
- 比较当前出现频率和最高出现频率
- 若当前出现频率等于最高出现频率,那么将数字加入结果集
- 若当前出现频率高于已知的最高出现频率,那么更新最高出现频率,并清空当前结果集后,再加入当前数字
Java解法(中序遍历+双指针法):
class Solution {public List<Integer> ans = new ArrayList<>();public int max_times = 1, times = 1;public TreeNode pre = null;public int[] findMode(TreeNode root) {this.dfs(root);return ans.stream().mapToInt(Integer::valueOf).toArray();}public void dfs(TreeNode cur){if(null == cur) return ;// 左this.dfs(cur.left);// 中// 记录当前数字的出现频率if(null != this.pre){if(cur.val == this.pre.val){this.times++;}else{this.times = 1;}}if(this.times == this.max_times){// 如果出现频率等于最高频率,那么将数字加入结果集this.ans.add(cur.val);}else if(this.times > this.max_times){// 如果出现频率高于已知的最高频率,那么更新最高频率,并清空当前结果集后再加入新的数字this.max_times = this.times;this.ans.clear();this.ans.add(cur.val);}this.pre = cur;//右this.dfs(cur.right);}
}
二叉树的最近公共祖先
题目详细:LeetCode.236
由题可知:
- 所有节点的值都是唯一的
- p、q 均存在于给定的二叉树中
一个节点也可以是它自己的祖先
所以我们可以先分析当前节点为最近公共祖先的情况有哪些(也就是如何判断该节点是否是p、q的最近公共祖先):
- 情况一: p 和 q 分别在左子树和右子树,那么当前节点即为最近公共祖先,直接返回 root
- 情况二:在右子树中找不到 p 或 q ( right == null ),那么说明 p 和 q 应都在左子树上,返回 left,在左子树中继续寻找
- 情况三:在左子树中找不到 p 或 q ( left == null ),那么说明 p 和 q 应都在右子树上,返回 right,在右子树中继续寻找
若我们基于深度优先遍历的递归算法进行解题,那么还会出现一种情况:
- 假如当 p 与当前节点相同时(p == root),那么 q 必然只能分布在其子树中,所以当前节点即为最近公共祖先,同理可得当(q == root)的情况。
通过分析最近公共祖先的三种基本情况,可知解题的关键在于递归分析节点 p 和 q 在每一个节点的左右子树分布情况
,所以我们可以利用递归算法,优先对当前节点的左右子树进行深度优先遍历,通过左右子树的返回结果来确定当前节点是否为最近公共祖先。
Java解法(递归):
class Solution {public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {if(root == null || p == root || q ==root)return root;TreeNode left = lowestCommonAncestor(root.left,p,q);TreeNode right = lowestCommonAncestor(root.right,p,q);return (right == null) ? left : (left == null) ? right : root;}
}