不要纠结,干就完事了,熟练度很重要!!!多练习,多总结!!!
特性篇
LeetCode 230. 二叉搜索树中第K小的元素
解题思路
二叉搜索树的中序遍历是结果是从小到大排列!
代码实现
class Solution {
int rank = 0;
int res;
public int kthSmallest(TreeNode root, int k) {
traverse(root, k);
return res;
}
public void traverse(TreeNode root, int k){
if(root == null){
return ;
}
traverse(root.left,k);
rank++;
if(k == rank){
res = root.val;
}
traverse(root.right, k);
}
}
LeetCode 538. 把二叉搜索树转换为累加树
解题思路
二叉搜索树中序遍历从小到大排列,左右节点反过来遍历,就是从大到小排列,那么维护一个变量统计累计和即可。
代码实现
class Solution {
int sum = 0;
public TreeNode convertBST(TreeNode root) {
traverse(root);
return root;
}
public void traverse(TreeNode root){
if(root == null){
return ;
}
traverse(root.right);
sum+=root.val;
root.val = sum;
traverse(root.left);
}
}
基操篇
LeetCode 98. 验证二叉搜索树
解题思路
注意本题二叉搜索树的合法性不仅是当前节点的左右子树需要满足条件,左右子树的子树也需要满足(即递归实现)。
代码实现
class Solution {
public boolean isValidBST(TreeNode root) {
return isValidBST(root, null,null);
}
public boolean isValidBST(TreeNode root, TreeNode min, TreeNode max){
if(root == null){
return true;
}
if(min != null && root.val <= min.val){
return false;
}
if(max != null && root.val >= max.val){
return false;
}
return isValidBST(root.left, min, root) && isValidBST(root.right, root, max);
}
}
LeetCode 700. 二叉搜索树中的搜索
代码实现
class Solution {
public TreeNode searchBST(TreeNode root, int val) {
if(root == null){
return null;
}
if(root.val == val){
return root;
}else if(root.val > val){
return searchBST(root.left, val);
}else {
return searchBST(root.right, val);
}
}
}
LeetCode 701. 二叉搜索树中的插入操作
代码实现
class Solution {
public TreeNode insertIntoBST(TreeNode root, int val) {
if(root == null){
return new TreeNode(val);
}
if(root.val > val){
root.left = insertIntoBST(root.left, val);
}else if(root.val < val){
root.right = insertIntoBST(root.right, val);
}
return root;
}
}
LCR 155. 将二叉搜索树转化为排序的双向链表
解题思路
代码实现
class Solution {
List<Node> res = new ArrayList<>();
public Node treeToDoublyList(Node root) {
if (root==null){
return root;
}
pre(root);
Node head=res.get(0);
Node pre=head;
for (int i=1;i<res.size();i++){
Node node=res.get(i);
node.left=pre;
pre.right=node;
pre=pre.right;
}
head.left=pre;
pre.right=head;
return head;
}
public List<Node> pre(Node root){
traverse(root);
return res;
}
private void traverse(Node root){
if(root == null){
return;
}
traverse(root.left);
res.add(root);
traverse(root.right);
}
}
构造篇
LeetCode 96. 不同的二叉搜索树
解题思路
比如给算法输入n = 5,也就是说用{1,2,3,4,5}这些数字去构造 BST。
首先,这棵 BST 的根节点总共有几种情况?
显然有 5 种情况对吧,因为每个数字都可以作为根节点。
比如说我们固定3作为根节点,这个前提下能有几种不同的 BST 呢?
根据 BST 的特性,根节点的左子树都比根节点的值小,右子树的值都比根节点的值大。
所以如果固定3作为根节点,左子树节点就是{1,2}的组合,右子树就是{4,5}的组合。
左子树的组合数和右子树的组合数乘积就是3作为根节点时的 BST 个数。
代码实现
class Solution {
int[][] memo;
public int numTrees(int n) {
memo = new int[n+1][n+1];
return numTrees(1, n);
}
public int numTrees(int i, int j){
if(i > j){
return 1;
}
if(memo[i][j] != 0){
return memo[i][j];
}
int res = 0;
for(int k = i;k <= j;k++){
int left = numTrees(i, k-1);
int right = numTrees(k+1, j);
res +=left*right;
}
memo[i][j] = res;
return res;
}
}
LeetCode 95. 不同的二叉搜索树 II
解题思路
思路同 《96. 不同的二叉搜索树》只不过本题需要返回树的节点,递归获取左右子树后,依次构造不同的子树。
代码实现
class Solution {
public List<TreeNode> generateTrees(int n) {
return generateTrees(1, n);
}
public List<TreeNode> generateTrees(int i, int j){
List<TreeNode> list = new LinkedList<>();
if(i > j){
list.add(null);
return list;
}
for(int k = i;k <=j;k++){
List<TreeNode> left = generateTrees(i, k-1);
List<TreeNode> right = generateTrees(k+1, j);
for(TreeNode l : left){
for(TreeNode r:right){
TreeNode root = new TreeNode(k);
root.left = l;
root.right = r;
list.add(root);
}
}
}
return list;
}
}
后序篇
LeetCode 1373. 二叉搜索子树的最大键值和
解题思路
现在我们想计算子树中 BST 的最大和,站在当前节点的视角,需要做什么呢?
1、首先我想知道以我为根的这棵树是不是 BST,所以我肯定得知道左右子树是不是合法的 BST。因为如果这俩儿子有一个不是 BST,以我为根的这棵树肯定不会是 BST 的,对吧。
2、如果左右子树都是合法的 BST,我得瞅瞅左右子树加上自己还是不是合法的 BST 了。因为按照 BST 的定义,当前节点的值应该大于左子树的最大值,小于右子树的最小值,否则就破坏了 BST 的性质。
3、因为题目要计算最大的节点之和,如果左右子树加上我自己还是一棵合法的 BST,也就是说以我为根的整棵树是一棵 BST,那我需要知道我们这棵 BST 的所有节点值之和是多少,方便和别的 BST 子树争个高下,对吧。
根据以上三点,站在当前节点的视角,需要知道以下具体信息:
- 左右子树是否是 BST。
- 左子树的最大值和右子树的最小值。
- 左右子树的节点值之和。
大致代码逻辑如下,此方法复杂度较高:
// 全局变量,记录最终结果
int maxSum = 0;
/* 主函数 */
public int maxSumBST(TreeNode root) {
traverse(root);
return maxSum;
}
/* 遍历二叉树 */
void traverse(TreeNode root) {
if (root == null) {
return;
}
/******* 前序遍历位置 *******/
// 判断左右子树是不是 BST
if (!isBST(root.left) || !isBST(root.right)) {
goto next;
}
// 计算左子树的最大值和右子树的最小值
int leftMax = findMax(root.left);
int rightMin = findMin(root.right);
// 判断以 root 节点为根的树是不是 BST
if (root.val <= leftMax || root.val >= rightMin) {
goto next;
}
// 如果条件都符合,计算当前 BST 的节点之和
int leftSum = findSum(root.left);
int rightSum = findSum(root.right);
int rootSum = leftSum + rightSum + root.val;
// 计算 BST 节点的最大和
this.maxSum = Math.max(maxSum, rootSum);
/**************************/
// 递归左右子树
next:
traverse(root.left);
traverse(root.right);
}
/* 计算以 root 为根的二叉树的最大值 */
int findMax(TreeNode root) {}
/* 计算以 root 为根的二叉树的最小值 */
int findMin(TreeNode root) {}
/* 计算以 root 为根的二叉树的节点和 */
int findSum(TreeNode root) {}
/* 判断以 root 为根的二叉树是否是 BST */
boolean isBST(TreeNode root) {}
代码实现
class Solution {
int maxSum = 0;
public int maxSumBST(TreeNode root) {
maxSum(root);
return maxSum;
}
public int[] maxSum(TreeNode root){
if(root == null){
return new int[]{1, Integer.MAX_VALUE, Integer.MIN_VALUE, 0};
}
int[] left = maxSum(root.left);
int[] right = maxSum(root.right);
int[] res = new int[4];
if(left[0] == 1 && right[0] == 1 && root.val > left[2] && root.val < right[1]){
res[0] = 1;
res[1] = Math.min(root.val, left[1]);
res[2] = Math.max(root.val, right[2]);
res[3] = root.val + left[3]+right[3];
maxSum = Math.max(res[3], maxSum);
}else{
res[0] = 0;
}
return res;
}
}
快速排序
LeetCode 912. 排序数组
解题思路
快速排序是先将一个元素排好序,然后再将剩下的元素排好序。
快速排序的核心无疑是 partition 函数, partition 函数的作用是在 nums[lo…hi] 中寻找一个分界点 p,通过交换元素使得 nums[lo…p-1] 都小于等于 nums[p],且 nums[p+1…hi] 都大于 nums[p]。
从二叉树的视角,我们可以把子数组 nums[lo…hi] 理解成二叉树节点上的值,srot 函数理解成二叉树的遍历函数。参照二叉树的前序遍历顺序。
代码实现
class Solution {
public int[] sortArray(int[] nums) {
sort(nums, 0, nums.length-1);
return nums;
}
public void sort(int[] nums, int lo, int hi){
if(lo > hi){
return ;
}
int j = partition(nums, lo, hi);
sort(nums, lo, j-1);
sort(nums, j+1, hi);
}
public int partition(int[] nums, int lo, int hi){
int pivot = nums[lo];
int i = lo+1, j = hi;
while(i <= j){
while(i < hi && nums[i] < pivot){
i++;
}
while(j > lo && nums[j] > pivot){
j--;
}
if(i >= j){
break;
}
swap(nums, i, j);
}
swap(nums, lo, j);
return j;
}
public void swap(int[] nums, int i, int j){
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
}
LeetCode 215. 数组中的第K个最大元素
解题思路
二叉堆思路简单,但复杂度较高。既然是找第K大元素,按照快速排序思路 ,前序遍历思维 每次找到一个有序的分界点,根据分界点去找结果的点,最后返回即可。
题目问「第 k 个最大的元素」,相当于数组升序排序后「排名第 n - k 的元素」,为了方便表述,后文另 k’ = n - k。
如何知道「排名第 k’ 的元素」呢?其实在快速排序算法 partition 函数执行的过程中就可以略见一二。
刚说了,partition 函数会将 nums[p] 排到正确的位置,使得 nums[lo…p-1] < nums[p] < nums[p+1…hi]:
这时候,虽然还没有把整个数组排好序,但我们已经让 nums[p] 左边的元素都比 nums[p] 小了,也就知道 nums[p] 的排名了。
那么我们可以把 p 和 k’ 进行比较,如果 p < k’ 说明第 k’ 大的元素在 nums[p+1…hi] 中,如果 p > k’ 说明第 k’ 大的元素在 nums[lo…p-1] 中。
代码实现
class Solution {
public int findKthLargest(int[] nums, int k) {
k = nums.length-k;
int low = 0, high = nums.length-1;
while(low <= high){
int p = partition(nums, low, high);
if(p > k){
high = p-1;
}else if(p < k){
low = p+1;
}else{
return nums[p];
}
}
return -1;
}
public int partition(int[] nums, int low, int high){
int pivot = nums[low];
int i = low+1, j = high;
while(i <= j){
while(i < high && nums[i] < pivot){
i++;
}
while(j > low && nums[j] > pivot){
j--;
}
if(i > j){
break;
}
swap(nums, i, j);
}
swap(nums, low, j);
return j;
}
public void swap(int[] nums, int i, int j){
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
}
int findKthLargest(int[] nums, int k) {
// 小顶堆,堆顶是最小元素
PriorityQueue<Integer>
pq = new PriorityQueue<>();
for (int e : nums) {
// 每个元素都要过一遍二叉堆
pq.offer(e);
// 堆中元素多于 k 个时,删除堆顶元素
if (pq.size() > k) {
pq.poll();
}
}
// pq 中剩下的是 nums 中 k 个最大元素,
// 堆顶是最小的那个,即第 k 个最大元素
return pq.peek();
}
总结
本题来源于Leetcode中 归属于二叉搜索树类型题目。
同许多在算法道路上不断前行的人一样,不断练习,修炼自己!
如有博客中存在的疑问或者建议,可以在下方留言一起交流,感谢各位!
觉得本博客有用的客官,可以给个点赞+收藏哦! 嘿嘿
喜欢本系列博客的可以关注下,以后除了会继续更新面试手撕代码文章外,还会出其他系列的文章!