目录
题二:Leetcode 100 如何判断两颗二叉树是否相同?
算法
树的遍历
题一:如何给二叉树的所有节点数值加一?
换个说法就是遍历每个节点,然后每个节点值 + 1;
实现方式就是树的遍历:【前、中、后序遍历】
public void traverseHelper(MyNode node){
if (node==null){
return;
}
//前序遍历
traverseHelper(node.left);
//中序遍历
traverseHelper(node.right);
//后续遍历
}
所以题一的解为:
public void NodePlus(TreeNode node) {
if (node == null) {
return;
}
//node.value = node.value + 1;
NodePlus(node.left);
//node.value = node.value + 1;
NodePlus(node.right);
node.value = node.value + 1;
}
题二:Leetcode 100 如何判断两颗二叉树是否相同?
这题的思路还是树的遍历,只是在遍历的基础上添加了判断的条件。对比一二题可以总结出树遍历的方法:递归
题二的解为:
public static boolean isSameBST(TreeNode one, TreeNode two) {
if (one == null && two == null) {
return true;
}
if (one == null || two == null) {
return false;
}
if (one.value != two.value) {
return false;
}
// 上面是判断条件,下面两条递归语句就是树的遍历的核心
boolean s1 = isSameBST(one.left, two.left);
boolean s2 = isSameBST(one.right, two.right);
return s1 && s2;
}
// 测试类
public static void main(String[] args) {
TreeNode node1 = new TreeNode(2);
node1.left = new TreeNode(1);
node1.right = new TreeNode(2);
TreeNode node2 = new TreeNode(2);
node2.left = new TreeNode(1);
node2.right = new TreeNode(3);
System.out.println(isSameBST(node1, node2));
}
题三:判断一棵树是否是排序二叉树
LeetCode:98. 验证二叉搜索树
有效 二叉搜索树定义如下:
-
节点的左子树只包含 小于 当前节点的数。
-
节点的右子树只包含 大于 当前节点的数。
-
所有左子树和右子树自身必须也是二叉搜索树。
思路:树的遍历,遍历每个节点时带上父节点的值,所以需要 bstHelper 方法提供父节点的信息。
public static boolean isBST(TreeNode treeNode) {
return bstHelper(treeNode, null, null);
}
private static boolean bstHelper(TreeNode currentNode, TreeNode maxNode, TreeNode minNode) {
if (currentNode == null) {
return true;
}
if (maxNode != null && currentNode.value => maxNode.value) {
return false;
}
if (minNode!=null &¤tNode.value <= minNode.value) {
return false;
}
boolean b1 = bstHelper(currentNode.left, currentNode, minNode);
boolean b2 = bstHelper(currentNode.right, maxNode, currentNode);
return b1 && b2;
}
// 测试类
public static void main(String[] args) {
TreeNode node = new TreeNode(2);
node.left = new TreeNode(1);
node.right = new TreeNode(4);
System.out.println(isBST(node));
}
题四:序列化、反序列化一个二叉树
剑指 Offer 37. 序列化二叉树
实现一个二叉树可以被序列化为一个字符串,并且可以将这个字符串反序列化为原始的树结构。
关键:不能只通过一种排序算法确定树的结构,确定树的结构需要两种遍历才可以【前中、前后】
结论:序列化的时候加上 空子树的标志
private static LinkedList<Integer> list = new LinkedList<>();
// 序列化:遍历每个节点值存到 linkedList 里
public static void serialize(TreeNode node){
if (node==null){
list.add(-1);
return;
}
list.add(node.value);
serialize(node.left);
serialize(node.right);
}
// 反序列化:从 linkedList 里取出值还原成树
public static TreeNode deSerialize(LinkedList<Integer> deList){
Integer first = deList.removeFirst();
if (first==-1){
return null;
}
TreeNode node = new TreeNode(first);
node.left = deSerialize(deList);
node.right = deSerialize(deList);
return node;
}
// 测试类
public static void main(String[] args) {
// 使用平衡二叉树(上一篇文章中有代码实现)
AVLTree tree = new AVLTree();
tree.insert(new TreeNode(2));
tree.insert(new TreeNode(1));
tree.insert(new TreeNode(3));
tree.insert(new TreeNode(6));
tree.insert(new TreeNode(10));
serialize(tree.getRoot());
TreeNode node = deSerialize(list);
tree.traverse(node); //这里使用中序遍历,所以输出结果为1 2 3 6 10
}
动态规划
应用场景:一般都是试求最值的问题
三要素:重叠子问题、最优子结构、状态转移方程
核心:先穷举出所有情况,然后优化问题,数学归纳
举例:斐波那序列
/**
* 斐波那契数列
*
* 定义:从第3项开始,每一项都等于前两项之和
*/
public static int simpleFib(int n) {
if (n == 0) {
return 0;
}
if (n == 1) {
return 1;
}
/**
* 此方法(暴力递归)会出现: 重叠子问题
* 例如 n = 4 时,要算出 n = 3 和 n = 2 的值
* n = 3 时,要算出 n = 2 和 n = 1 的值
* 这里面 n = 2 值的问题重复了
*/
return simpleFib(n - 1) + simpleFib(n - 2);
}
【重叠子问题】:
暴力递归 算法的时间复杂度:子问题个数 * 解决一个子问题所需要的时间 O(2^n)
对上述重叠子问题进行优化:存储子问题结果
public static int cacheFib(int n) {
// 将子问题结果存储到 HashMap 里面
Map<Integer, Integer> cache = new HashMap<>();
return fibHelper(n, cache);
}
private static int fibHelper(int n, Map<Integer, Integer> cache) {
if (n == 0) {
return 0;
}
if (n == 1) {
return 1;
}
Integer val = cache.get(n);
if (val != null) {
return val;
}
int i = fibHelper(n - 1, cache) + fibHelper(n - 2, cache);
cache.put(n, i);
return i;
}
经过优化之后,时间复杂度退回到常数级别 O(1)
还有一个问题,什么是状态转移方程?
【状态转移方程】:将 f(n) 想成一个状态,则此状态是通过 n-1 的状态与 n-2 的状态转移得到了。
simpleFib(n) = simpleFib(n - 1) + simpleFib(n - 2);
题一:凑零钱问题
LeetCode:322. 零钱兑换
题意:给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
例:
count = 11;coins{ 1, 2, 5 }
找规律:
coin = 1 | coin = 2 | coin = 5 | |
---|---|---|---|
1 | 1个(1*1) | ||
2 | 2个(1*2) | 1个(2 * 1) | |
3 | 3个(1*3) | 2个(2 * 1+1 * 1) | |
4 | 4个(1*4) | 2个(2 * 2) | |
5 | 5个(1 * 5) | 2个(2 * 2 + 1 * 1) | 1个(5 * 1) |
6 | 6个(1 * 6) | 3个(2 * 3) | 2个(5 * 1 + 1 * 1) |
7 | 7个(1*7) | 4个(2 * 3 + 1 * 1) | 2个(5 * 1 + 2 * 1) |
8 | 8个(1*8) | 4个(2 * 4) | 3个(5 * 1 + 2 * 1 + 1 * 1) |
9 | 9个(1*9) | 5个(2 * 4 + 1 * 1) | 3个(5 * 1 + 2 * 2) |
10 | 10个(1*10) | 5个(2 * 5) | 2个(5 * 2) |
11 | 11个(1*11) | 6个(2 * 5 + 1 * 1) | 3个(5 * 2 + 1 * 1) |
要想求出金额为 11 的最少硬币数,只需求出金额为 10 的最少硬币数(2)然后再加 1 = 3
同理要想求金额为 8 的最小硬币数,只需求出金额为 7 的最小硬币数(2)然后再加 1 = 3
【最优子结构】:向上面这种:子问题之间状态独立,且有最优解的结构
public static int coinsChange(int[] coins, int totalMoney) {
// 动态规划都有:边界问题
if (coins.length == 0) {
return -1;
}
// 数组用于存储每个 count 的最小硬币数
int[] memo = new int[totalMoney + 1];
memo[0] = 0;
// 第一重循环为 count 总金额循环
for (int i = 1; i <= totalMoney; i++) {
int min = Integer.MAX_VALUE;
// 第二重循环为 coin 硬币面值循环
for (int j = 0; j < coins.length; j++) {
int k = i - coins[j];
if (k >= 0 && memo[k] < min) {
// 要想求出金额为 11 的最少硬币数,
// 只需求出金额为 10 的最少硬币数然后再 + 1
min = memo[k] + 1;
}
}
memo[i] = min;
}
return memo[totalMoney] == Integer.MAX_VALUE ? -1 : memo[totalMoney];
}