问题总览
题解
基本操作
[搜索]700. 二叉搜索树中的搜索
class Solution {
public TreeNode searchBST(TreeNode root, int val) {
if (root == null || root.val == val) {
return root;
}
if (root.val > val) {
return searchBST(root.left, val);
} else {
return searchBST(root.right, val);
}
}
}
// 时间复杂度:O(h),类似于二分查找了,就是树的高度
// 空间复杂度:O(h)
[插入]701. 二叉搜索树中的插入操作
貌似并没有考虑要对树进行平衡。
class Solution {
public TreeNode insertIntoBST(TreeNode root, int val) {
if (root == null) {
return new TreeNode(val);
}
if (root.val < val) {
root.right = insertIntoBST(root.right, val);
}
if (root.val > val) {
root.left = insertIntoBST(root.left, val);
}
return root;
}
}
// 时间复杂度:O(h),和查找一样的
// 空间复杂度:O(h)
[删除]450. 删除二叉搜索树中的节点
class Solution {
public TreeNode deleteNode(TreeNode root, int key) {
if (root == null) {
return root;
}
if (root.val == key) {
// 找到了
if (root.left == null && root.right == null) {
// 这个节点没有子节点,那直接删除这个节点就可以
return null;
}
if (root.left == null) {
// 这个节点只有右节点,这个时候把它的右节点顶上来,代替root,相当于就是删除了root
return root.right;
}
if (root.right == null) {
// 这个节点只有左节点,这个时候把它的左节点顶上来,代替root,相当于就是删除了root
return root.left;
}
// 这个节点同时有左右子树,此时
TreeNode minNode = getMin(root.right);
root.val = minNode.val;
root.right = deleteNode(root.right, minNode.val);
} else if (root.val < key) {
// 去右子树继续找
root.right = deleteNode(root.right, key);
} else {
// 去左子树继续找
root.left = deleteNode(root.left, key);
}
return root;
}
private TreeNode getMin(TreeNode root) {
TreeNode p = root;
while (p.left != null) {
p = p.left;
}
return p;
}
}
[验证]98. 验证二叉搜索树
class Solution {
boolean ans = true;
public boolean isValidBST(TreeNode root){
dfs(root, null, null);
return ans;
}
public void dfs(TreeNode root, Integer leftMax, Integer rightMin) {
if (root == null) {
return;
}
if (leftMax != null && leftMax <= root.val) {
ans = false;
return;
}
if (rightMin != null && rightMin >= root.val) {
ans = false;
return;
}
dfs(root.left, root.val, rightMin);
dfs(root.right, leftMax, root.val);
}
}
构造二叉搜索树
class Solution {
int[][] memo;
List<TreeNode> ans;
public List<TreeNode> generateTrees(int n) {
ans = new ArrayList<>();
return dfs(1, n);
}
public List<TreeNode> dfs(int left, int right) {
List<TreeNode> res = new LinkedList<>();
if (left > right) {
res.add(null);
return res;
}
for (int i = left; i <= right; i++) {
List<TreeNode> lefts = dfs(left, i - 1);
List<TreeNode> rights = dfs(i + 1, right);
for (TreeNode l : lefts) {
for (TreeNode r : rights) {
TreeNode root = new TreeNode();
root.left = l;
root.right = r;
root.val = i;
res.add(root);
}
}
}
return res;
}
}
class Solution {
int[][] memo;
public int numTrees(int n) {
// 用备忘录删除重复问题
memo = new int[n + 1][n + 1];
return count(1, n);
}
public int count(int left, int right) {
// 空也是一种情况,所以需要返回1
if (left > right) {
return 1;
}
if (memo[left][right] != 0) {
return memo[left][right];
}
int res = 0;
for (int i = left; i <= right; i++) {
// 左子树和右子树的可能数量相乘,就是当前节点可能的二叉搜索树
res += count(left, i - 1) * count(i + 1, right);
}
memo[left][right] = res;
return res;
}
}
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
int n = nums.length;
// 已经按照升序排列
// 转换成高度平衡的二叉树,返回的是根节点
// 1,2,3,4,5,6,7,8
// 1,2,3,4,5,6,7
return build(nums, 0, n - 1);
}
public TreeNode build(int[] nums, int start, int end) {
if (start > end) {
return null;
}
int mid = start + (end - start) / 2;
TreeNode root = new TreeNode(nums[mid]);
root.left = build(nums, start, mid - 1);
root.right = build(nums, mid + 1, end);
return root;
}
}
比有序数组转换二叉搜索树难的地方在于,链表不是随机读取的。
class Solution {
public TreeNode sortedListToBST(ListNode head) {
return build(head, null);
}
public TreeNode build(ListNode left, ListNode right) {
// 正因为选择了左闭又开,这边的退出条件在相等的时候需要返回null,否则相等应该返回自身
if (left == right) {
return null;
}
ListNode mid = getMid(left, right);
TreeNode root = new TreeNode(mid.val);
// 选择了左闭右开的结构,因为找到上一个节点有点麻烦
// 如果是左闭右闭,这边的写法应该是mid.pre
root.left = build(left, mid);
root.right = build(mid.next, right);
return root;
}
public ListNode getMid(ListNode left, ListNode right) {
ListNode slow = left;
ListNode fast = left;
while (fast != right && fast.next != right) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
}
- 时间复杂度:O(nlogn),因为每一个元素都需要二分查找中点,查找一次是logn查找n次就是nlogn
- 空间复杂度:O(logn)
// 优化的解法
class Solution {
ListNode globalHead;
public TreeNode sortedListToBST(ListNode head) {
globalHead = head;
int length = getLength(head);
return buildTree(0, length - 1);
}
public int getLength(ListNode head) {
int len = 0;
while (head != null) {
len++;
head = head.next;
}
return len;
}
// 利用中序遍历的原理,因为一个二叉搜索树中序遍历的结果肯定是升序的
// 我们反过来想,我们构造的这个二叉搜索树如果中序遍历得到的结果就是入参的head
// 而buildTree不断的二分递归,找到的第一个树,其实就是二叉搜索树最左边的元素,也就是head,依次往后面中序遍历,每一个节点和入参元素的位置一一对应
public TreeNode buildTree(int left, int right) {
if (left > right) {
return null;
}
int mid = (left + right + 1) / 2;
TreeNode root = new TreeNode();
root.left = buildTree(left, mid - 1);
// 只能在中序遍历的位置赋值
root.val = globalHead.val;
globalHead = globalHead.next;
root.right = buildTree(mid + 1, right);
return root;
}
}
- 时间复杂度:O(logn)
- 空间复杂度:O(logn)
利用中序遍历
class Solution {
int ans = 0;
int rank = 0;
// 利用二叉搜索树中序遍历为有序的特性
public int kthSmallest(TreeNode root, int k) {
dfs(root, k);
return ans;
}
public void dfs(TreeNode root, int k) {
if (root == null) {
return;
}
dfs(root.left, k);
//中序遍历的位置
rank++;
if (k == rank) {
ans = root.val;
return;
}
dfs(root.right, k);
}
}
class Solution {
int ans = 0;
int rank = 0;
public int kthLargest(TreeNode root, int k) {
dfs(root, k);
return ans;
}
public void dfs(TreeNode root, int k) {
if (root == null) {
return;
}
dfs(root.right, k);
rank++;
if (rank == k) {
ans = root.val;
return;
}
dfs(root.left, k);
}
}
class BSTIterator {
TreeNode cur;
Deque<TreeNode> stack;
public BSTIterator(TreeNode root) {
stack = new LinkedList<>();
//初始化必须为root节点,不然无法进行dfs
cur = root;
}
public int next() {
while (cur != null) {
stack.push(cur);
cur = cur.left;
}
// 从左/中的节点中取
cur = stack.pop();
int res = cur.val;
// cur作为左已经是最左了,下一个要找cur的中,那就是要去stack里pop
// 在此之前,cur作为中,需要检查cur的right在不在
cur = cur.right;
return res;
}
public boolean hasNext() {
// cur不等于null,说明肯定有next,因为遍历完一次后,cur就指向right了
// cur等于null,只能说明没有right不能说明没有next
// stack为空,可能这个树全是右节点
return cur != null || !stack.isEmpty();
}
}
class Solution {
public Node inorderSuccessor(Node x) {
// 如果有右子树,那么直接返回右子树中最小的节点
if (x.right != null) {
x = x.right;
while (x.left != null) x = x.left;
return x;
}
// 如果没有右子树,此时需要找祖先节点,但是如果当前节点是祖先节点的右子树,说明这个祖先节点是前驱不是后继,那么得继续往上找
// 走到根节点(x.parent == null)也没发现合适的,那么返回null
// 因为如果当前节点位于根节点的左子树中,那么肯定可以找到根节点
// 只有当前节点位于根节点的右侧时,才找不到根节点
while (x.parent != null && x == x.parent.right) x = x.parent;
return x.parent;
}
}
- 时间复杂度:O(logn)
- 空间复杂度:O(1)
转换为其他数据结构
相同问题:1038. 从二叉搜索树到更大和树
class Solution {
public TreeNode convertBST(TreeNode root) {
dfs(root);
return root;
}
int sum = 0;
// 利用倒序的中序遍历,是将元素从大到小
// 累加树的需求是计算大于或等于该元素的所有元素的和
public void dfs(TreeNode root) {
if (root == null) {
return;
}
dfs(root.right);
sum += root.val;
root.val = sum;
dfs(root.left);
}
}
利用了二叉搜索树的特性,左边的子树都比根节点小,右边的子树都比根节点大,而后续遍历是左、右、中,根节点肯定是末尾的元素。我们只需要找到左子树和右子树,再按照这个条件去检查子树是否符合二叉搜索树的条件就可以。
class Solution {
public boolean verifyPostorder(int[] postorder) {
if (postorder == null) {
return true;
}
return verifyPostorder(postorder, 0, postorder.length - 1);
}
public boolean verifyPostorder(int[] postorder, int start, int end) {
// base条件,不会存在start小于end的情况,因为rightRoot都是大于等于start的
if (start >= end) {
return true;
}
int root = end;
int rightRoot = start;
for (int i = end - 1; i >= start; i--) {
// 找到右子树的根节点
if (postorder[i] < postorder[root]) {
rightRoot = i + 1;
break;
}
}
for (int i = start; i < rightRoot; i++) {
// 左子树必须都小于根节点
if (postorder[i] > postorder[root]) {
return false;
}
}
// 左右子树都需要满足后序
return verifyPostorder(postorder, start, rightRoot - 1) && verifyPostorder(postorder, rightRoot, end - 1);
}
}
// 时间复杂度:O(n^2),最坏的情况是,每一轮都要遍历所有节点
// 空间复杂度:O(height),栈的空间
// 无脑遍历
class Solution {
List<Integer> list2;
boolean ans;
public boolean twoSumBSTs(TreeNode root1, TreeNode root2, int target) {
list2 = new ArrayList<>();
dfs2(root2);
dfs1(root1, target);
return ans;
}
private void dfs1(TreeNode root1, int target) {
if (root1 == null) {
return;
}
if (list2.contains(target - root1.val)) {
ans = true;
return;
}
dfs1(root1.left, target);
dfs1(root1.right, target);
}
private void dfs2(TreeNode root2) {
if (root2 == null) {
return;
}
list2.add(root2.val);
dfs2(root2.left);
dfs2(root2.right);
}
}
// 时间复杂度:O(logn)
// 空间复杂度:O(n)
Morris遍历
// 显式的中序遍历,先将中序遍历的结果用数组保存下来
class Solution {
public void recoverTree(TreeNode root) {
List<Integer> nums = new ArrayList<>();
dfs(root, nums);
int x = -1, y = -1;
int len = nums.size();
for (int i = 0; i < len - 1; i++) {
if (nums.get(i) > nums.get(i + 1)) {
y = i + 1;
if (x == -1) {
x = i;
}
}
}
x = nums.get(x);
y = nums.get(y);
dfs(root, x, y);
}
private void dfs(TreeNode root, List<Integer> nums) {
if (root == null) {
return;
}
dfs(root.left, nums);
nums.add(root.val);
dfs(root.right, nums);
}
private void dfs(TreeNode root, int x, int y) {
if (root == null) {
return;
}
dfs(root.left, x, y);
if (root.val == x) {
root.val = y;
} else if (root.val == y) {
root.val = x;
}
dfs(root.right, x, y);
}
}
- 时间复杂度:O(N)
- 空间复杂度:O(H)
// 该方法属于隐士中序遍历,在遍历的过程中记录下需要交换的节点
class Solution {
TreeNode x = null;
TreeNode y = null;
TreeNode pre = null;
public void recoverTree(TreeNode root) {
dfs(root);
// 交换两个节点
swap(x, y);
}
private void dfs(TreeNode root) {
if (root == null) {
return;
}
dfs(root.left);
// 判断pre和root的关系,如果pre大于root,说明找到了逆序的
if (pre != null && pre.val > root.val) {
y = root;
// 因为是中序遍历,首先遍历到的肯定是第一个逆序对,此时给x赋值
if (x == null) {
x = pre;
}
}
// pre始终指向root的前一个节点
pre = root;
dfs(root.right);
}
public void swap(TreeNode x, TreeNode y) {
int tmp = x.val;
x.val = y.val;
y.val = tmp;
}
}