一.树的遍历 前中后序遍历,层次遍历 前中后序遍历如下:与其说对应代码如下,这种代码被称作前中后序遍历 理解:遵循“根左右”,“左根右”,“左右跟的顺序”
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
return zhongxubianli(root, list);
}
private List<Integer> zhongxubianli(TreeNode root, List<Integer> list) {
if (root == null) return list;
// 前序遍历
// list.add(root.val);
zhongxubianli(root.left, list);
// 中序遍历
// list.add(root.val);
zhongxubianli(root.right, list);
// 后序遍历
// list.add(root.val);
return list;
}
}
层次遍历,即BFS宽度优先,一层一层遍历,通过队列存储该层节点,遍历处理再将其子节点依次添加进来处理即可 // 队列的创建与接口
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
if (root == null) return new LinkedList<List<Integer>>();
List<List<Integer>> list = new LinkedList<List<Integer>>();
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()) {
int n = queue.size();
List<Integer> res = new LinkedList<>();
for (int i = 0; i < n; i++) {
TreeNode node = queue.poll();
res.add(node.val);
if (node.left!=null) {
queue.offer(node.left);
}
if (node.right!=null) {
queue.offer(node.right);
}
}
list.add(res);
}
return list;
}
}
二叉搜索树,即左边比根小,右边比根大,中序遍历后是有序链表
// no.96 利用卡特兰数
class Solution {
public int numTrees(int n) {
int[] dp = new int[n+1];
dp[0]=1;
dp[1]=1;
for (int i = 2; i <= n; i++) {
for (int j = 1; j <= i; j++) {
dp[i] += dp[j - 1] * dp[i - j];
}
}
return dp[n];
}
}
// no.98
class Solution {
public boolean isValidBST(TreeNode root) {
List<Integer> list = new ArrayList<>();
isValidBST1(root, list);
for (int i = 1; i < list.size(); i++) {
if (list.get(i)<= list.get(i-1)) return false;
}
return true;
}
private void isValidBST1(TreeNode root, List<Integer> num) {
if (root == null) return;
isValidBST1(root.left, num);
num.add(root.val);
isValidBST1(root.right, num);
}
}
no.105 前序遍历+中序遍历构造二叉树
// 要点:节约时间,先用map将中序数组值与下标存储起来
// 要点2:线序遍历首节点便是根节点,找到在中序中的位置,左边的范围为左子树,右边为右子树,继续遍历。这个范围同时也是确定前序数组中的下一个根,需要4个辅助参数记录前中数组的范围
class Solution {
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
public TreeNode buildTree(int[] preorder, int[] inorder) {
if(preorder == null || preorder.length == 0) return null;
for (int i = 0; i < inorder.length; i++) {
map.put(inorder[i], i);
}
return buildTree1(preorder, inorder, 0, preorder.length-1, 0 ,inorder.length-1);
}
private TreeNode buildTree1(int[] preorder, int[] inorder, int l_p, int r_p, int l_i, int r_i) {
if (l_p>r_p) return null;
TreeNode node = new TreeNode(preorder[l_p]);
int newRootIndex = map.get(preorder[l_p]);
int step = newRootIndex-l_i;
node.left = buildTree1(preorder, inorder, l_p+1, l_p+step, l_i ,newRootIndex-1);
node.right = buildTree1(preorder, inorder, l_p+step+1, r_p, newRootIndex+1 ,r_i);
return node;
}
}
no.114 二叉树展开为列表:先通过前序遍历记录至列表,再遍历列表赋值 no.124 二叉树的最大路径和
// 如果当前节点是最大路径的根结点,那么就等于root.val加上左右两边的值(前提是两边大于0)
// 如果当前不是最大路径的根节点,就需要返回左右两边最大的那个值
// 技巧:即在回溯过程也要计算结果,两边不耽误(许多题中都需要这么使用)
// 技巧:在递归函数中定义值,就相当于是该值是以当前为根节点得出的结果
class Solution {
int res = Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) {
if (root == null) return 0;
maxPathSum1(root);
return res;
}
public int maxPathSum1(TreeNode root) {
if (root == null) return 0;
int left = Math.max(maxPathSum1(root.left), 0)+root.val;
int right = Math.max(maxPathSum1(root.right), 0)+ root.val;
res = Math.max(res, left + right - root.val);
return Math.max(left, right);
}
}
no236. 二叉树的最近公共祖先 // 借助后序遍历,因为顺序是左右根 // 找到p或q就返回,直到回溯到p,q都找到, 那就是结果 // 可能是p或q就是祖先,代码已给出
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || root == p || root == q) return root;
TreeNode node1 = lowestCommonAncestor(root.left, p, q);
TreeNode node2 = lowestCommonAncestor(root.right, p, q);
// 借助后序遍历,回溯到同时找到pq时便是返回结果
if (node2==null) return node1; // 特例,p或q是对方的祖先
if (node1 == null) return node2; // 特例,p或q是对方的祖先
if (node1 != null && node2 != null) return root;
return null;
}
}
no297. 二叉树的序列化与反序列化 // 前序遍历或层次遍历,因为反序列化要从根节点开始。然后再反过来计算即可,要注意对于需要把null值也序列化,不然反推不回来
public class Codec {
public String serialize(TreeNode root) {
StringBuilder sb = new StringBuilder();
BFS(root, sb);
return sb.toString();
}
private void BFS(TreeNode root, StringBuilder sb) {
if (root == null)
return;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
for (int i = 0; i < queue.size(); i++) {
TreeNode node = queue.poll();
if (node == null) sb.append("null,");
else {
sb.append(node.val + ",");
queue.offer(node.left);
queue.offer(node.right);
}
}
}
}
// Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
if (data == null || data.isEmpty())
return null;
String[] str = data.split(",");
int index = 0;
TreeNode node = new TreeNode(Integer.parseInt(str[index]));
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(node);
while (true) {
for (int i = 0; i < queue.size(); i++) {
TreeNode root = queue.poll();
if (++index >= str.length-1) return node;
if (!"null".equals(str[index])) {
TreeNode left = new TreeNode(Integer.parseInt(str[index]));
root.left = left;
queue.offer(left);
}
if (++index >= str.length-1) return node;
if (!"null".equals(str[index])) {
TreeNode right = new TreeNode(Integer.parseInt(str[index]));
root.right = right;
queue.offer(right);
}
}
}
}
}
no337. 打家劫舍3 首先并不是135层或者246层加起来就可以,可能存在孙子节点+叔叔节点才是最大值 那么对于一个节点来说,存在选,不选两种状态并取其中的最大值 如果选,那么当前最大值就等于当前值+孙子节点的最大值(注意:这不代表孙子节点是一定选择的) 如果不选,就等于儿子节点的最大值(同样的,也不代表儿子节点就是被选了的) 此外呢,注意到有些节点是可能重复计算的,可以用map记录下
class Solution {
Map<TreeNode, Integer> map = new HashMap<>();
public int rob(TreeNode root) {
if (root ==null) return 0;
if (map.get(root) != null) return map.get(root);
int money = root.val;
if (root.left != null) {
money += rob(root.left.left)+rob(root.left.right);
}
if (root.right != null) {
money+=rob(root.right.left)+rob(root.right.right);
}
int max = Math.max(money, rob(root.left)+rob(root.right));
map.put(root, max);
return max;
}
}
no437. 路径总和(前缀和) // 向下递归通过map记录前缀和(节点以上的和) // 向上回溯清除当前前缀和 // 当前前缀和前去目标target如果有值加起来就行
class Solution {
Map<Integer, Integer> currentSumMap = new HashMap<>();
public int pathSum (TreeNode root, int targetSum) {
currentSumMap.put(0, 1);
return pathSum1(root, targetSum, 0);
}
public int pathSum1(TreeNode root, int targetSum, int currentSum) {
if (root == null) return 0;
int res = 0;
currentSum+= root.val;
res+=currentSumMap.getOrDefault((currentSum-targetSum), 0);
currentSumMap.put(currentSum, currentSumMap.getOrDefault(currentSum, 0) +1);
res+=pathSum1(root.left, targetSum, currentSum);
res+=pathSum1(root.right, targetSum, currentSum);
currentSumMap.put(currentSum, currentSumMap.getOrDefault(currentSum, 0) -1);
return res;
}
}
no538. 把二叉搜索树转换为累加树 // 中序遍历变形 -> 右中左。依次相加即可
class Solution {
public TreeNode convertBST(TreeNode root) {
convertBST1(root);
return root;
}
int res = 0;
public int convertBST1(TreeNode root) {
if (root == null) return 0;
convertBST1(root.right);
res+=root.val;
root.val = res;
convertBST1(root.left);
return res;
}
}
no543. 二叉树的直径 // 根节点并非最大,可能是子节点 // 需要回溯记录最大值
class Solution {
int ans = 0;
public int diameterOfBinaryTree(TreeNode root) {
if (root == null) return 0;
TreeNode node = new TreeNode(0);
diameterOfBinaryTree1(root, node);
return node.val;
}
public int diameterOfBinaryTree1(TreeNode root, TreeNode ans) {
if (root == null) return 0;
int left = diameterOfBinaryTree1(root.left, ans);
int right = diameterOfBinaryTree1(root.right, ans);
ans.val = Math.max(left+right, ans.val);
return Math.max(left,right) + 1;
}
}
个人总结
- 理解前中序层次遍历,对于给出的树,需要借助遍历去计算,至于何种遍历,根据题意是否有计算顺序,没有可默认前序
- 对于最大最小种类等题,根节点可能不符合题意,这种需要同时处理两种情况(可能:需要一个额外值记录[不受递归影响])
- 递归过程中定义变量求出当前值并记录比较(当作符合题意根节点
- 正常返回当前节点值给父节点(非符合题意根节点