目录
1. T530. 二叉搜索树的最小绝对差
T530:给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值 。
差值是一个正数,其数值等于两值之差的绝对值。
提示:
-
树中节点的数目范围是 [2, 104]
-
0 <= Node.val <= 105
S:
还是跟day20的最后一题:验证二叉搜索树一样,先从最直观的思路入手,不断优化~
1.1 法1、递归
1.1.1 版本Ⅰ、直观版
C++:
vector<int> vec;
int getMinimumDifference(TreeNode* root) {
if (!root) return 0;
traversal(root);
if (vec.size() < 2) return 0;
int diff= INT_MAX;
for (int i = 1; i < vec.size(); ++i) {
if (res > vec[i] - vec[i - 1]) {
res = vec[i] - vec[i - 1];
}
}
return diff;
}
void traversal(TreeNode* node) {
if (!node) return;
traversal(node->left);
// if (node->left && diff > node->val - node->left->val) {
// diff = node->val - node->left->val;
// }//这样只能比较本节点及其左子节点,比如这个例子就会导致错误:[1,null,2]->INT_MAX
vec.push_back(node->val);
traversal(node->right);
}
Java:
List<Integer> nums;
public int getMinimumDifference(TreeNode root) {
if (root == null) return 0;
nums = new ArrayList<>();
traversal(root);
int diff = Integer.MAX_VALUE;
for (int i = 1; i < nums.size(); ++i) {
if (diff > nums.get(i) - nums.get(i - 1)) {
diff = nums.get(i) - nums.get(i - 1);
}
}
return diff;
}
void traversal(TreeNode root) {
if (root == null) return;
traversal(root.left);
nums.add(root.val);
traversal(root.right);
}
1.1.2 版本Ⅱ、最终版
C++:
int diff = INT_MAX;
TreeNode* pre = nullptr;
int getMinimumDifference(TreeNode* root) {
if (!root) return 0;
traversal(root);
return diff;
}
void traversal(TreeNode* node) {
if (!node) return;
traversal(node->left);
if (pre && diff > node->val - pre->val) {
diff = node->val - pre->val;
}
pre = node;
traversal(node->right);
}
Java:
TreeNode pre = null;
int diff = Integer.MAX_VALUE;
public int getMinimumDifference(TreeNode root) {
if (root == null) return 0;
traversal(root);
return diff;
}
void traversal(TreeNode root) {
if (root == null) return;
traversal(root.left);
if (pre != null && diff > root.val - pre.val) {
diff = root.val - pre.val;
}
pre = root;
traversal(root.right);
}
1.2 法2、迭代
1.2.1 版本Ⅰ、普通迭代
int getMinimumDifference(TreeNode* root) {
if (!root) return 0;
int diff = INT_MAX;
stack<TreeNode*> st;
TreeNode* cur = root;
TreeNode* pre = nullptr;
while (cur || !st.empty()) {
// cur = st.top();
if (cur) {
// st.pop();
st.push(cur);
cur = cur->left;
} else {
// st.pop();
cur = st.top();
st.pop();
if (pre && diff > cur->val - pre->val) {
diff = cur->val - pre->val;
}
pre = cur;
cur = cur->right;
}
}
return diff;
}
Java:
public int getMinimumDifference(TreeNode root) {
if (root == null) return 0;
int diff = Integer.MAX_VALUE;
TreeNode pre = null;
TreeNode cur = root;
Stack<TreeNode> stack = new Stack<>();
while (cur != null || !stack.isEmpty()) {
if (cur != null) {
stack.push(cur);
cur = cur.left;
} else {
cur = stack.pop();
if (pre != null && diff > cur.val - pre.val) {
diff = cur.val - pre.val;
}
pre = cur;
cur = cur.right;
}
}
return diff;
}
1.2.2 版本Ⅱ、统一迭代
C++:
int getMinimumDifference(TreeNode* root) {
if (!root) return 0;
int diff = INT_MAX;
stack<TreeNode*> st;
st.push(root);
TreeNode* cur;
TreeNode* pre = nullptr;
while (!st.empty()) {
cur = st.top();
if (cur) {
st.pop();
if (cur->right) st.push(cur->right);
st.push(cur);
st.push(nullptr);
if (cur->left) st.push(cur->left);
} else {
st.pop();
cur = st.top();
st.pop();
if (pre && diff > cur->val - pre->val) {
diff = cur->val - pre->val;
}
pre = cur;
}
}
return diff;
}
Java:
public int getMinimumDifference(TreeNode root) {
if (root == null) return 0;
int diff = Integer.MAX_VALUE;
TreeNode pre = null;
TreeNode cur = null;
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
cur = stack.pop();
if (cur != null) {
if (cur.right != null) stack.push(cur.right);
stack.push(cur);
stack.push(null);
if (cur.left != null) stack.push(cur.left);
} else {
cur = stack.pop();
if (pre != null && diff > cur.val - pre.val) {
diff = cur.val - pre.val;
}
pre = cur;
}
}
return diff;
}
1.3 总结
只要题目中说明了是二叉搜索树BST,就要灵活运用BST的特性。在递归或迭代过程中,记录前缀指针,以方便将其用于和当前指针比较,是一个相当好用的技巧~
2. T501:二叉搜索树中的众数
T501:给你一个含重复值的二叉搜索树(BST)的根节点 root ,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。
如果树中有不止一个众数,可以按 任意顺序 返回。
假定 BST 满足如下定义:
-
结点左子树中所含节点的值 小于等于 当前节点的值
-
结点右子树中所含节点的值 大于等于 当前节点的值
-
左子树和右子树都是二叉搜索树
提示:
-
树中节点的数目在范围 [1, 104] 内
-
-105 <= Node.val <= 105
进阶:你可以不使用额外的空间吗?(假设由递归产生的隐式调用栈的开销不被计算在内)
S:遍历本身不是什么问题,主要是统计众数的逻辑要想清楚!
2.1 法1、递归
2.1.1 版本Ⅰ:for普通二叉树
简单地说,就是遍历二叉树同时统计各值(key)出现频率(value),按照频率排序,将最高的众数返回
C++:vector<pair<int, int>> 和 unordered_map<int, int> 应该就算使用了额外的空间
vector<int> findMode(TreeNode* root) {
vector<int> res;
if (!root) return res;
unordered_map<int, int> map;// key:元素,value:出现频率
searchBST(root, map);
vector<pair<int, int>> vec(map.begin(), map.end());
sort(vec.begin(), vec.end(), cmp);// 降序排序
res.push_back(vec[0].first);
for (int i = 1; i < vec.size(); ++i) {
if (vec[i].second == vec[0].second) {
res.push_back(vec[i].first);
} else break;// 如果一上来就频率小了,后面自然就不用看了
}
return res;
}
private:
void searchBST(TreeNode* node, unordered_map<int, int>& map) {
if (!node) return;
map[node->val]++;
searchBST(node->left, map);
searchBST(node->right, map);
}
// 指定降序的排序规则
bool static cmp(const pair<int, int> a, const pair<int, int> b) {
return a.second - b.second > 0;
}
Java:
public int[] findMode(TreeNode root) {
List<Integer> res = new ArrayList<>();
if (root == null) return res.stream().mapToInt(Integer::intValue).toArray();
Map<Integer, Integer> map = new HashMap<>();
searchBST(root, map);
List<Map.Entry<Integer, Integer>> mapList = map.entrySet().stream()
.sorted((a, b) -> b.getValue() - a.getValue())//指定排序规则
.collect(Collectors.toList());//Set无序,所以必须转为List
res.add(mapList.get(0).getKey());
// for (Integer entry : mapList) {//明显不行(无法跳过索引0)
for (int i = 1; i < mapList.size(); ++i) {
if (mapList.get(i).getValue() == mapList.get(0).getValue()) {
res.add(mapList.get(i).getKey());
} else break;
}
return res.stream().mapToInt(Integer::intValue).toArray();
}
private void searchBST(TreeNode node, Map<Integer, Integer> map) {
if (node == null) return;
map.put(node.val, map.getOrDefault(node.val, 0) + 1);
searchBST(node.left, map);
searchBST(node.right, map);
}
2.1.2 版本Ⅱ:二叉搜索树
利用容器“动态清零”的思路,实现只用遍历二叉树一次,而且还不用额外的内存空间(Map)
C++:
public:
vector<int> findMode(TreeNode* root) {
if (!root) return res;
searchBST(root);
return res;
}
private:
vector<int> res;
int count, maxCount;
TreeNode* pre = nullptr;
void searchBST(TreeNode* cur) {
if (!cur) return;
searchBST(cur->left);
// 中
if (!pre) {
count = 1;
} else if (pre->val == cur->val){
++count;
} else {// 与前一个节点数值不同
count = 1;
}
pre = cur;//别漏!
//下面的不要并入上一串花括号!否则:[0]->error[null]
if (count == maxCount) {//多众数
res.push_back(cur->val);
}
if (count > maxCount) {
maxCount = count;
res.clear();//之前result里的元素都失效了
res.push_back(cur->val);
}
searchBST(cur->right);
}
Java:
int count = 0, maxCount = 0;
TreeNode pre = null;
List<Integer> res;
public int[] findMode(TreeNode root) {
res = new ArrayList<>();
// if (root == null) return res.stream().mapToInt(Integer::intValue).toArray();//不需要
searchBST(root);
return res.stream().mapToInt(Integer::intValue).toArray();
}
private void searchBST(TreeNode cur) {
if (cur == null) return;
searchBST(cur.left);
if (pre == null) {
// pre = cur;
count = 1;
} else {
if (pre.val == cur.val) {
++count;
} else {//别漏!
count = 1;
}
}
//下面的不要并入上一行的花括号!否则:[0]->error[null]
if (count == maxCount) {
res.add(cur.val);
}
if (count > maxCount) {
maxCount = count;
res.clear();
res.add(cur.val);
}
pre = cur;
searchBST(cur.right);
}
2.2 法2、迭代
vector<int> findMode(TreeNode* root) {
vector<int> res;
if (!root) return res;
int count = 0, maxCount = 0;
TreeNode* cur = root;
TreeNode* pre = nullptr;
stack<TreeNode*> st;
while (cur || !st.empty()) {
if (cur) {
st.push(cur);
cur = cur->left;
} else {
cur = st.top();
st.pop();
if (!pre) {
count = 1;
} else if (pre->val == cur->val) {
++count;
} else {
count = 1;
}
if (count == maxCount) {
res.push_back(cur->val);
}
if (count > maxCount) {
maxCount = count;
res.clear();
res.push_back(cur->val);
}
pre = cur;
cur = cur->right;
}
}
return res;
}
Java:
public int[] findMode(TreeNode root) {
List<Integer> res = new ArrayList<>();
if (root == null) return res.stream().mapToInt(Integer::intValue).toArray();
int count = 0, maxCount = 0;
TreeNode pre = null;
TreeNode cur = root;
Stack<TreeNode> stack = new Stack<>();
while (cur != null || !stack.isEmpty()) {
if (cur != null) {
stack.push(cur);
cur = cur.left;
} else {
cur = stack.pop();
if (pre == null) {
count = 1;
} else {
if (pre.val == cur.val) {
++count;
} else {
count = 1;
}
}
if (count == maxCount) {
res.add(cur.val);
}
if (count > maxCount) {
maxCount = count;
res.clear();
res.add(cur.val);
}
pre = cur;
cur = cur.right;
}
}
return res.stream().mapToInt(Integer::intValue).toArray();
}
3. T236:二叉树的最近公共祖先
T236:给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
提示:
-
树中节点数目在范围 [2, 105] 内。
-
-109 <= Node.val <= 109
-
所有 Node.val 互不相同 。
-
p != q
-
p 和 q 均存在于给定的二叉树中。
S:要想找到祖先,自然需要自底向上查找,后序遍历(左右中)就是天然的自下而上回溯过程。
判断逻辑是:如果递归遍历遇到q,就将q返回,遇到p就将p返回,那么 如果左右子树的返回值都不为空,说明此时的中节点,一定是q 和p 的最近祖先。
C++:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (!root || root == p || root == q) return root;
TreeNode* left = lowestCommonAncestor(root->left, p, q);
TreeNode* right = lowestCommonAncestor(root->right, p, q);
if (left && right) return root;
else if (left && !right) return left;
else if (!left && right) return right;
// else if (!left && !right) //这样写明的话->无默认返回值
else return nullptr;
}
最后那一串逻辑可以简化一下:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (!root || root == p || root == q) return root;
TreeNode* left = lowestCommonAncestor(root->left, p, q);
TreeNode* right = lowestCommonAncestor(root->right, p, q);
if (left && right) return root;
if (!left) return right;
return left;
}
Java:
public TreeNode lowestCommonAncestor(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 && right != null) return root;
// 理解版
// else if (left != null && right == null) return left;
// else if (left == null && right != null) return right;
// else return null;
//简化版
if (left == null) return right;
return left;
}