1.不同的二叉搜索树(96-中)
题目描述:给你一个整数 n
,求恰由 n
个节点组成且节点值从 1
到 n
互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。
示例:
输入:n = 3 输出:5
思路:注意二叉排序树,因此如果选择 i 为根节点,那么左子树共有 i - 1 个节点,右子树共有 n - i 个节点,每种根节点对应的二叉树的数量肯定是两个子树情况的乘积,一共有n个这样的根节点。
- dp[i] 含义:i个节点组成的二叉搜索树数量
- 状态转移方程:dp[i] += dp[j - 1] * dp[i - j]
事实上,状态转移方程在数学上被称为卡塔兰数,便于计算的定义如下:
C0 = 1, Cn+1 = 2*Cn*(2*n + 1)/(n + 2)
代码:
public int numTrees(int n) { int[] dp = new int[n + 1]; dp[0] = 1; // 空树也是一种二叉搜索树 for (int i = 1; i <= n; i++) { for (int j = 1; j <= i; j++) { dp[i] += dp[j - 1] * dp[i - j]; } } return dp[n]; } // 数学:卡特兰数 public int numTrees(int n) { long ans = 1; for (int i = 0; i < n; i++) { ans = ans * 2 * (2 * i + 1) / (i + 2); } return (int)ans; }
2.不同的二叉搜索树II(95-中)
题目描述:给你一个整数 n
,求恰由 n
个节点组成且节点值从 1
到 n
互不相同的 二叉搜索树 ?返回满足题意的二叉搜索树。
示例:
输入:n = 3 输出:[[1,null,2,null,3],[1,null,3,2],[2,1,3],[3,1,null,null,2],[3,2,null,1]]
思路:二叉搜索树,对于任意一个节点大于左子树节点, 小于右子树节点。
- 主要思想为分治递归树,然后左右子树列表构建结果树。
- 注意:当n=0时,返回[], 但是F[0]]需要记录[[]]。
代码:
public List<TreeNode> generateTrees(int n) { if (n < 1) { return new LinkedList<TreeNode>(); } return generateSubTrees(1, n); } private List<TreeNode> generateSubTrees(int s, int e) { List<TreeNode> ret = new LinkedList<>(); if (s > e) { //显然保证左子树或者右子树为空,正确构建树依赖下边左右子树列表的遍历,如果列表为空,那么循环不能进行。 ret.add(null); return ret; } // 枚举所有可行的根节点 for (int i = s; i <= e; ++i) { //1.分解:获取所有可行的左右子树集合 List<TreeNode> leftSubTrees = generateSubTrees(s, i - 1); List<TreeNode> rightSubTrees = generateSubTrees(i + 1, e); //2.合并:合并左右子树,需要创建leftSubTrees.size() * rightSubTrees.size()个根节点 for (TreeNode left : leftSubTrees) { for (TreeNode right : rightSubTrees) { TreeNode root = new TreeNode(i); root.left = left; root.right = right; ret.add(root); } } } return ret; }
3.恢复二叉搜索树(99-难)
题目描述:给你二叉搜索树的根节点 root ,该树中的两个节点被错误地交换。请在不改变其结构的情况下,恢复这棵树。
进阶:使用 O(n) 空间复杂度的解法很容易实现。你能想出一个只使用常数空间的解决方案吗?
示例:
输入:root = [1,3,null,null,2] 输出:[3,1,null,null,2] 解释:3 不能是 1 左孩子,因为 3 > 1 。交换 1 和 3 使二叉搜索树有效。
思路:显然利用中序序列的有序性,使用递归、迭代与morris(左子树为空和不为空两种情况)遍历解决,参考二叉树的中序遍历。注意交换的两种情况:
- 相邻两个数字交换(一组逆序对)
- 不相邻数字交换(两组逆序对)
代码:
// 递归解法 TreeNode first = null, second = null, pre = null; public void recoverTree(TreeNode root) { inOrder(root); int tmp = first.val; first.val = second.val; second.val = tmp; } public void inOrder(TreeNode root) { if (root == null) return; inOrder(root.left); if (pre != null && root.val < pre.val) { if (first == null) { first = pre; second = root; } else { second = root; } } pre = root; inOrder(root.right); } // 迭代解法 TreeNode first = null, second = null, pre = null; public void recoverTree(TreeNode root) { inOrder(root); int tmp = first.val; first.val = second.val; second.val = tmp; } public void inOrder(TreeNode root) { if (root == null) return; Stack<TreeNode> stack = new Stack<>(); while (root != null || !stack.isEmpty()) { while (root != null) { stack.push(root); root = root.left; } root = stack.pop(); if (pre != null && root.val < pre.val) { if (first == null) { first = pre; second = root; } else { second = root; } } pre = root; root = root.right; } } // morris解法 public void recoverTree(TreeNode root) { TreeNode first = null; TreeNode second = null; TreeNode cur = root; TreeNode pre_new = null; while (cur != null) { // 情况 1 if (cur.left == null) { /*******************************************************/ if (pre_new != null && cur.val < pre_new.val) { if (first == null) { first = pre_new; second = cur; } else { second = cur; } } pre_new = cur; /*******************************************************/ cur = cur.right; } else { // 找左子树最右边的节点 TreeNode pre = cur.left; while (pre.right != null && pre.right != cur) { pre = pre.right; } // 情况 2.1(第一次到达左子树的最右节点,改变指针) if (pre.right == null) { pre.right = cur; cur = cur.left; } // 情况 2.2(第二到达,恢复指针) if (pre.right == cur) { pre.right = null; // 这里可以恢复为 null /*******************************************************/ if (pre_new != null && cur.val < pre_new.val) { if (first == null) { first = pre_new; second = cur; } else { second = cur; } } pre_new = cur; /*******************************************************/ cur = cur.right; } } } int temp = first.val; first.val = second.val; second.val = temp; }
4.验证二叉搜索树(98-中)
题目描述:验证一棵树是否为二叉搜索树:左子树节点都小于当前节点,右子树节点都大于当前节点,左右子树本身也是二叉搜索树。
示例:
输入: 5 / \ 1 4 / \ 3 6 输出: false 解释: 输入为: [5,1,4,null,null,3,6]。 根节点的值为 5 ,但是其右子节点值为 4 。
思路:可以使用额外空间,可以通过二叉搜索树中序遍历的有序性进行判断,实现简单。
这里使用递归进行判断:
- 终止条件:root == null return true(空树也是二叉搜索树)
- 递归逻辑:当前节点与上一个节点值的大小关系
- 与二叉树的中序遍历相同:先左子树,当前节点,右子树
代码:
List<Integer> res = new ArrayList<>(); public boolean isValidBST(TreeNode root) { inOrder(root); for (int i = 1; i < res.size(); i++) { if (res.get(i) <= res.get(i - 1)) { return false; } } return true; } private void inOrder(TreeNode root) { if (root == null) return; inOrder(root.left); res.add(root.val); inOrder(root.right); } //设置一个pre变量存上一个节点的值 long pre = Long.MIN_VALUE; public boolean isValidBST(TreeNode root) { if (root == null) return true; if (!isValidBST(root.left)) { return false; } //访问当前节点,当前节点小于或者等于中序遍历的上一个节点,不满足,否则继续 if (root.val <= pre) { return false; } pre = root.val; return isValidBST(root.right); }
7.不同的二叉搜索树(96-中)
题目描述:给你一个整数 n
,求恰由 n
个节点组成且节点值从 1
到 n
互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。
示例:
输入:n = 3 输出:5
思路:注意二叉排序树,因此如果选择 i 为根节点,那么左子树共有 i - 1 个节点,右子树共有 n - i 个节点,每种根节点对应的二叉树的数量肯定是两个子树情况的乘积,一共有n个这样的根节点。
- dp[i] 含义:i个节点组成的二叉搜索树数量
- 状态转移方程:dp[i] += dp[j - 1] * dp[i - j]
事实上,状态转移方程在数学上被称为卡塔兰数,便于计算的定义如下:
C0 = 1, Cn+1 = 2*Cn*(2*n + 1)/(n + 2)
代码:
public int numTrees(int n) { int[] dp = new int[n + 1]; dp[0] = 1; // 空树也是一种二叉搜索树 for (int i = 1; i <= n; i++) { for (int j = 1; j <= i; j++) { dp[i] += dp[j - 1] * dp[i - j]; } } return dp[n]; } // 数学:卡特兰数 public int numTrees(int n) { long ans = 1; for (int i = 0; i < n; i++) { ans = ans * 2 * (2 * i + 1) / (i + 2); } return (int)ans; }
8.不同的二叉搜索树II(95-中)
题目描述:给你一个整数 n
,求恰由 n
个节点组成且节点值从 1
到 n
互不相同的 二叉搜索树 ?返回满足题意的二叉搜索树。
示例:
输入:n = 3 输出:[[1,null,2,null,3],[1,null,3,2],[2,1,3],[3,1,null,null,2],[3,2,null,1]]
思路:二叉搜索树,对于任意一个节点大于左子树节点, 小于右子树节点。
- 主要思想为分治递归树,然后左右子树列表构建结果树。
- 注意:当n=0时,返回[], 但是F[0]]需要记录[[]]。
代码:
public List<TreeNode> generateTrees(int n) { if (n < 1) { return new LinkedList<TreeNode>(); } return generateSubTrees(1, n); } private List<TreeNode> generateSubTrees(int s, int e) { List<TreeNode> ret = new LinkedList<>(); if (s > e) { //显然保证左子树或者右子树为空,正确构建树依赖下边左右子树列表的遍历,如果列表为空,那么循环不能进行。 ret.add(null); return ret; } // 枚举所有可行的根节点 for (int i = s; i <= e; ++i) { //1.分解:获取所有可行的左右子树集合 List<TreeNode> leftSubTrees = generateSubTrees(s, i - 1); List<TreeNode> rightSubTrees = generateSubTrees(i + 1, e); //2.合并:合并左右子树,需要创建leftSubTrees.size() * rightSubTrees.size()个根节点 for (TreeNode left : leftSubTrees) { for (TreeNode right : rightSubTrees) { TreeNode root = new TreeNode(i); root.left = left; root.right = right; ret.add(root); } } } return ret; }
8.恢复二叉搜索树(99-难)
题目描述:给你二叉搜索树的根节点 root ,该树中的两个节点被错误地交换。请在不改变其结构的情况下,恢复这棵树。
进阶:使用 O(n) 空间复杂度的解法很容易实现。你能想出一个只使用常数空间的解决方案吗?
示例:
输入:root = [1,3,null,null,2] 输出:[3,1,null,null,2] 解释:3 不能是 1 左孩子,因为 3 > 1 。交换 1 和 3 使二叉搜索树有效。
思路:显然利用中序序列的有序性,使用递归、迭代与morris(左子树为空和不为空两种情况)遍历解决,参考二叉树的中序遍历。注意交换的两种情况:
- 相邻两个数字交换(一组逆序对)
- 不相邻数字交换(两组逆序对)
代码:
// 递归解法 TreeNode first = null, second = null, pre = null; public void recoverTree(TreeNode root) { inOrder(root); int tmp = first.val; first.val = second.val; second.val = tmp; } public void inOrder(TreeNode root) { if (root == null) return; inOrder(root.left); if (pre != null && root.val < pre.val) { if (first == null) { first = pre; second = root; } else { second = root; } } pre = root; inOrder(root.right); } // 迭代解法 TreeNode first = null, second = null, pre = null; public void recoverTree(TreeNode root) { inOrder(root); int tmp = first.val; first.val = second.val; second.val = tmp; } public void inOrder(TreeNode root) { if (root == null) return; Stack<TreeNode> stack = new Stack<>(); while (root != null || !stack.isEmpty()) { while (root != null) { stack.push(root); root = root.left; } root = stack.pop(); if (pre != null && root.val < pre.val) { if (first == null) { first = pre; second = root; } else { second = root; } } pre = root; root = root.right; } } // morris解法 public void recoverTree(TreeNode root) { TreeNode first = null; TreeNode second = null; TreeNode cur = root; TreeNode pre_new = null; while (cur != null) { // 情况 1 if (cur.left == null) { /*******************************************************/ if (pre_new != null && cur.val < pre_new.val) { if (first == null) { first = pre_new; second = cur; } else { second = cur; } } pre_new = cur; /*******************************************************/ cur = cur.right; } else { // 找左子树最右边的节点 TreeNode pre = cur.left; while (pre.right != null && pre.right != cur) { pre = pre.right; } // 情况 2.1(第一次到达左子树的最右节点,改变指针) if (pre.right == null) { pre.right = cur; cur = cur.left; } // 情况 2.2(第二到达,恢复指针) if (pre.right == cur) { pre.right = null; // 这里可以恢复为 null /*******************************************************/ if (pre_new != null && cur.val < pre_new.val) { if (first == null) { first = pre_new; second = cur; } else { second = cur; } } pre_new = cur; /*******************************************************/ cur = cur.right; } } } int temp = first.val; first.val = second.val; second.val = temp; }
8.验证二叉搜索树(98-中)
题目描述:验证一棵树是否为二叉搜索树:左子树节点都小于当前节点,右子树节点都大于当前节点,左右子树本身也是二叉搜索树。
示例:
输入: 5 / \ 1 4 / \ 3 6 输出: false 解释: 输入为: [5,1,4,null,null,3,6]。 根节点的值为 5 ,但是其右子节点值为 4 。
思路:可以使用额外空间,可以通过二叉搜索树中序遍历的有序性进行判断,实现简单。
这里使用递归进行判断:
- 终止条件:root == null return true(空树也是二叉搜索树)
- 递归逻辑:当前节点与上一个节点值的大小关系
- 与二叉树的中序遍历相同:先左子树,当前节点,右子树
代码:
List<Integer> res = new ArrayList<>(); public boolean isValidBST(TreeNode root) { inOrder(root); for (int i = 1; i < res.size(); i++) { if (res.get(i) <= res.get(i - 1)) { return false; } } return true; } private void inOrder(TreeNode root) { if (root == null) return; inOrder(root.left); res.add(root.val); inOrder(root.right); } //设置一个pre变量存上一个节点的值 long pre = Long.MIN_VALUE; public boolean isValidBST(TreeNode root) { if (root == null) return true; if (!isValidBST(root.left)) { return false; } //访问当前节点,当前节点小于或者等于中序遍历的上一个节点,不满足,否则继续 if (root.val <= pre) { return false; } pre = root.val; return isValidBST(root.right); }
7.不同的二叉搜索树(96-中)
题目描述:给你一个整数 n
,求恰由 n
个节点组成且节点值从 1
到 n
互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。
示例:
输入:n = 3 输出:5
思路:注意二叉排序树,因此如果选择 i 为根节点,那么左子树共有 i - 1 个节点,右子树共有 n - i 个节点,每种根节点对应的二叉树的数量肯定是两个子树情况的乘积,一共有n个这样的根节点。
- dp[i] 含义:i个节点组成的二叉搜索树数量
- 状态转移方程:dp[i] += dp[j - 1] * dp[i - j]
事实上,状态转移方程在数学上被称为卡塔兰数,便于计算的定义如下:
C0 = 1, Cn+1 = 2*Cn*(2*n + 1)/(n + 2)
代码:
public int numTrees(int n) { int[] dp = new int[n + 1]; dp[0] = 1; // 空树也是一种二叉搜索树 for (int i = 1; i <= n; i++) { for (int j = 1; j <= i; j++) { dp[i] += dp[j - 1] * dp[i - j]; } } return dp[n]; } // 数学:卡特兰数 public int numTrees(int n) { long ans = 1; for (int i = 0; i < n; i++) { ans = ans * 2 * (2 * i + 1) / (i + 2); } return (int)ans; }
8.不同的二叉搜索树II(95-中)
题目描述:给你一个整数 n
,求恰由 n
个节点组成且节点值从 1
到 n
互不相同的 二叉搜索树 ?返回满足题意的二叉搜索树。
示例:
输入:n = 3 输出:[[1,null,2,null,3],[1,null,3,2],[2,1,3],[3,1,null,null,2],[3,2,null,1]]
思路:二叉搜索树,对于任意一个节点大于左子树节点, 小于右子树节点。
- 主要思想为分治递归树,然后左右子树列表构建结果树。
- 注意:当n=0时,返回[], 但是F[0]]需要记录[[]]。
代码:
public List<TreeNode> generateTrees(int n) { if (n < 1) { return new LinkedList<TreeNode>(); } return generateSubTrees(1, n); } private List<TreeNode> generateSubTrees(int s, int e) { List<TreeNode> ret = new LinkedList<>(); if (s > e) { //显然保证左子树或者右子树为空,正确构建树依赖下边左右子树列表的遍历,如果列表为空,那么循环不能进行。 ret.add(null); return ret; } // 枚举所有可行的根节点 for (int i = s; i <= e; ++i) { //1.分解:获取所有可行的左右子树集合 List<TreeNode> leftSubTrees = generateSubTrees(s, i - 1); List<TreeNode> rightSubTrees = generateSubTrees(i + 1, e); //2.合并:合并左右子树,需要创建leftSubTrees.size() * rightSubTrees.size()个根节点 for (TreeNode left : leftSubTrees) { for (TreeNode right : rightSubTrees) { TreeNode root = new TreeNode(i); root.left = left; root.right = right; ret.add(root); } } } return ret; }
8.恢复二叉搜索树(99-难)
题目描述:给你二叉搜索树的根节点 root ,该树中的两个节点被错误地交换。请在不改变其结构的情况下,恢复这棵树。
进阶:使用 O(n) 空间复杂度的解法很容易实现。你能想出一个只使用常数空间的解决方案吗?
示例:
输入:root = [1,3,null,null,2] 输出:[3,1,null,null,2] 解释:3 不能是 1 左孩子,因为 3 > 1 。交换 1 和 3 使二叉搜索树有效。
思路:显然利用中序序列的有序性,使用递归、迭代与morris(左子树为空和不为空两种情况)遍历解决,参考二叉树的中序遍历。注意交换的两种情况:
- 相邻两个数字交换(一组逆序对)
- 不相邻数字交换(两组逆序对)
代码:
// 递归解法 TreeNode first = null, second = null, pre = null; public void recoverTree(TreeNode root) { inOrder(root); int tmp = first.val; first.val = second.val; second.val = tmp; } public void inOrder(TreeNode root) { if (root == null) return; inOrder(root.left); if (pre != null && root.val < pre.val) { if (first == null) { first = pre; second = root; } else { second = root; } } pre = root; inOrder(root.right); } // 迭代解法 TreeNode first = null, second = null, pre = null; public void recoverTree(TreeNode root) { inOrder(root); int tmp = first.val; first.val = second.val; second.val = tmp; } public void inOrder(TreeNode root) { if (root == null) return; Stack<TreeNode> stack = new Stack<>(); while (root != null || !stack.isEmpty()) { while (root != null) { stack.push(root); root = root.left; } root = stack.pop(); if (pre != null && root.val < pre.val) { if (first == null) { first = pre; second = root; } else { second = root; } } pre = root; root = root.right; } } // morris解法 public void recoverTree(TreeNode root) { TreeNode first = null; TreeNode second = null; TreeNode cur = root; TreeNode pre_new = null; while (cur != null) { // 情况 1 if (cur.left == null) { /*******************************************************/ if (pre_new != null && cur.val < pre_new.val) { if (first == null) { first = pre_new; second = cur; } else { second = cur; } } pre_new = cur; /*******************************************************/ cur = cur.right; } else { // 找左子树最右边的节点 TreeNode pre = cur.left; while (pre.right != null && pre.right != cur) { pre = pre.right; } // 情况 2.1(第一次到达左子树的最右节点,改变指针) if (pre.right == null) { pre.right = cur; cur = cur.left; } // 情况 2.2(第二到达,恢复指针) if (pre.right == cur) { pre.right = null; // 这里可以恢复为 null /*******************************************************/ if (pre_new != null && cur.val < pre_new.val) { if (first == null) { first = pre_new; second = cur; } else { second = cur; } } pre_new = cur; /*******************************************************/ cur = cur.right; } } } int temp = first.val; first.val = second.val; second.val = temp; }
8.验证二叉搜索树(98-中)
题目描述:验证一棵树是否为二叉搜索树:左子树节点都小于当前节点,右子树节点都大于当前节点,左右子树本身也是二叉搜索树。
示例:
输入: 5 / \ 1 4 / \ 3 6 输出: false 解释: 输入为: [5,1,4,null,null,3,6]。 根节点的值为 5 ,但是其右子节点值为 4 。
思路:可以使用额外空间,可以通过二叉搜索树中序遍历的有序性进行判断,实现简单。
这里使用递归进行判断:
- 终止条件:root == null return true(空树也是二叉搜索树)
- 递归逻辑:当前节点与上一个节点值的大小关系
- 与二叉树的中序遍历相同:先左子树,当前节点,右子树
代码:
List<Integer> res = new ArrayList<>(); public boolean isValidBST(TreeNode root) { inOrder(root); for (int i = 1; i < res.size(); i++) { if (res.get(i) <= res.get(i - 1)) { return false; } } return true; } private void inOrder(TreeNode root) { if (root == null) return; inOrder(root.left); res.add(root.val); inOrder(root.right); } //设置一个pre变量存上一个节点的值 long pre = Long.MIN_VALUE; public boolean isValidBST(TreeNode root) { if (root == null) return true; if (!isValidBST(root.left)) { return false; } //访问当前节点,当前节点小于或者等于中序遍历的上一个节点,不满足,否则继续 if (root.val <= pre) { return false; } pre = root.val; return isValidBST(root.right); }