遍历问题
105-从前序与中序遍历序列构造二叉树
题意分析:
- 根据树的遍历重构二叉树。
解题思路:
- 先根据 preorder 遍历找到 root 节点,找到 root 节点在 inorder 的索引位置 idx,然后递归构造左右子树(难点在于如何根据 idx 分隔左右子树区间)。
注意点:
扩展:
代码:
public TreeNode buildTree(int[] preorder, int[] inorder) {
return build(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1);
}
private TreeNode build(int[] preorder, int preStart, int preEnd, int[] inorder, int inStart, int inEnd) {
// 递归结束条件
if (preStart > preEnd) {
return null;
}
System.out.println(Arrays.toString(preorder) + ": prestart=" + preStart + "; preend=" + preEnd);
System.out.println(Arrays.toString(inorder) + " instart=" + inStart +"; inend=" + inEnd);
System.out.println();
int rootVal = preorder[preStart];
// 记录中序遍历中rootVal 对应的位置
int idx = -1;
// 这里每次递归调用时都会重复遍历inorder 数组,可以通过 HashMap 保持 val 对应的 idx 信息
for (int i = inStart; i <= inEnd; i++) {
if (inorder[i] == rootVal) {
idx = i;
break;
}
}
// 根据先序遍历找到新树的 root 节点
TreeNode root = new TreeNode(rootVal);
// left_len = idx - inStart
root.left = build(preorder, preStart + 1, preStart + (idx - inStart), inorder, inStart, idx - 1);
root.right = build(preorder, preStart + (idx - inStart) + 1, preEnd, inorder, idx + 1, inEnd);
if (root.left != null && root.right != null)
System.out.println("root=" + root.val + "; root.left=" + root.left.val + "; root.right=" + root.right.val);
return root;
}
106-从中序与后序遍历序列构造二叉树
题意分析:
- 和 105 题目差不多,条件序列不同而已。
解题思路:
- 先根据 postorder 遍历找到 root 节点,找到 root 节点在 inorder 的索引位置 idx,然后递归构造左右子树(难点在于如何根据 idx 分隔左右子树区间)。
注意点:
扩展:
代码:
public TreeNode buildTree(int[] inorder, int[] postorder) {
return build(inorder, 0, inorder.length - 1, postorder, 0, postorder.length - 1);
}
private TreeNode build(int[] inorder, int inStart, int inEnd, int[] postorder, int postStart, int postEnd) {
if (postStart > postEnd) {
return null;
}
System.out.println(Arrays.toString(inorder) + ": inStart=" + inStart + "; inEnd=" + inEnd);
System.out.println(Arrays.toString(postorder) + " postStart=" + postStart +"; postEnd=" + postEnd);
System.out.println();
int rootVal = postorder[postEnd];
// 记录中序遍历中rootVal 对应的位置
int idx = -1;
for (int i = inStart; i <= inEnd; i++) {
if (inorder[i] == rootVal) {
idx = i;
break;
}
}
// 根据先序遍历找到新树的 root 节点
TreeNode root = new TreeNode(rootVal);
root.left = build(inorder, inStart, idx - 1, postorder, postStart, postStart + (idx - inStart) - 1);
root.right = build(inorder, idx + 1, inEnd, postorder, postStart + (idx - inStart), postEnd - 1);
System.out.println("root=" + root.val + "; root.left=" + root.left + "; root.right=" + root.right);
if (root.left != null && root.right != null)
System.out.println("root=" + root.val + "; root.left=" + root.left.val + "; root.right=" + root.right.val);
return root;
}
二叉树的先序遍历
代码:
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
preorder(root, res);
return res;
}
public void preorder(TreeNode root, List res) {
if (root == null) {
return;
}
res.add(root.val);
preorder(root.left, res);
preorder(root.right, res);
}
public List<Integer> preorderTraversal2(TreeNode root) {
List<Integer> res = new ArrayList<>();
Deque<TreeNode> stack = new LinkedList<>();
if (root == null) {
return res;
}
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
res.add(node.val);
if (node.right != null) {
stack.push(node.right);
}
if (node.left != null) {
stack.push(node.left);
}
}
return res;
}
94-二叉树的中序遍历
代码:
/**
* 递归思路:
* 1、递归退出条件
* 2、递归体
*/
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
inorder(root, res);
return res;
}
private void inorder(TreeNode root, List<Integer> res) {
if (root == null) {
return;
}
inorder(root.left, res);
res.add(root.val);
inorder(root.right, res);
}
/**
* 非递归思路:
* 1、手动维护栈,什么时候进栈,什么时候出栈
* @param root
* @return
*/
public List<Integer> inorderTraversal2(TreeNode root) {
List<Integer> res = new ArrayList<>();
Deque<TreeNode> stack = new LinkedList<>();
while (root != null || !stack.isEmpty()) {
while (root != null) {
stack.push(root);
root = root.left;
}
TreeNode node = stack.pop();
res.add(node.val);
root = node.right;
}
return res;
}
二叉树的后序遍历
代码:
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
postorder(root, res);
return res;
}
public void postorder(TreeNode root, List res) {
if (root == null) {
return;
}
postorder(root.left, res);
postorder(root.right, res);
res.add(root.val);
}
public List<Integer> postorderTraversal2(TreeNode root) {
List<Integer> res = new ArrayList<>();
Deque<TreeNode> stack = new LinkedList<>();
TreeNode pre = null; // 用于记录上一次访问的节点
while (root != null || !stack.isEmpty()) {
while (root != null) {
stack.push(root);
root = root.left;
}
TreeNode node = stack.pop();
if (node.right != null) {
stack.push(node);
root = node.right;
} else if (node.right == null || node.right == pre){
res.add(node.val);
pre = node;
root = null;
}
}
return res;
}
102-二叉树的层序遍历
代码:
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
if (root == null) {
return res;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int size = queue.size();
List<Integer> list = new ArrayList<>();
// 逐层记录元素
while (size > 0) {
TreeNode node = queue.poll();
list.add(node.val);
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
size--;
}
// 一层元素遍历完了添加到 res 中
res.add(list);
}
return res;
}
路径问题
1104-二叉树寻路
题意分析:
- 打印树根节点到 label 的路径,只不过是树奇偶层级数据进行了反向。
解题思路:
- 按层级遍历完全二叉树,根据对称关系对树进行处理。
注意点:
扩展:
代码:
/**
* 解题思路
* 特点:完全二叉树
* 1、每一行的元素区间为 [2^(level-1), 2^level -1]
* @param label
* @return
*/
public List<Integer> pathInZigZagTree(int label) {
// 根据 label 得到所在行数
int level = 1;
// 每一行的最大元素为 2^(row) - 1
while (label > (Math.pow(2, level) - 1)) {
level++;
}
// 因为偶数行是反着的,所以在寻路前先和对称元素交换,在处理偶数行时再交换过来。
if (level % 2 == 0) {
label = (int)(Math.pow(2, level - 1) + Math.pow(2, level)) - 1 -label;
}
List<Integer> path = new ArrayList<>();
while (level > 0) {
// 奇偶行分别处理,奇数行正常处理,偶数行对称处理
if (level % 2 == 0) {
// 拿到对称的元素。(对称两个元素和是固定的)
int val = (int)(Math.pow(2, level - 1) + Math.pow(2, level)) - 1 -label;
path.add(val);
} else {
path.add(label);
}
// label 往父节点走一次,并且行数减 1
label = label / 2;
level--;
}
Collections.reverse(path);
return path;
}
257-二叉树的所有路径
题意分析:
- 打印数的所有路径,显然是要遍历树。
解题思路:
- dfs 深度遍历,遍历到树节点开始回溯。
注意点:
扩展:
- 能否使用 bfs 广度遍历实现?
代码:
/**
* dfs深度遍历 + 回溯思想(找完一个往回找)
*/
List<List<Integer>> res = new LinkedList<>();
public List<List<Integer>> binaryTreePaths(TreeNode root) {
LinkedList<Integer> path = new LinkedList<>();
dfs(root, path);
return res;
}
public void dfs(TreeNode root, LinkedList path) {
if (root == null) {
return;
}
path.offer(root.val);
if (root.left == null && root.right == null) {
res.add(new LinkedList<>(path));
// return;
}
dfs(root.left, path);
dfs(root.right, path);
path.pollLast();
}
112-路径总和 I
题意分析:
- 判断是否存在从根节点到叶子节点存在和为 sum 的路径。
解题思路:
- dfs 递归遍历,不断减去节点值递归。
- 层序遍历,记录每一层所有节点的和,直到叶子节点,判断是否存在和为 sum 的叶子节点。
注意点:
扩展:
- 如何输出所有路径?
代码:
/**
* 先序遍历。
*
* 思考:如果存在路径,如何打印出路径?
*
* @param root
* @param sum
* @return
*/
public boolean hasPathSum(TreeNode root, int sum) {
// base case
if (root == null) {
return false;
}
sum -= root.val;
if (root.left == null && root.right == null && sum == 0) {
return true;
}
boolean left = hasPathSum(root.left, sum);
boolean right = hasPathSum(root.right, sum);
return left || right;
}
113-路径总和 II
题意分析:
- 输出所有和为 sum 的路径,该路径从根节点到叶子节点。
解题思路:
- dfs。路径问题建议 dfs 算法,可以结合回溯思想遍历到每个叶子节点。
- bfs(层序遍历)。记录每一层节点中到达该节点的路径总和,并且还要记录其父亲节点。
注意点:
- bfs 思路要记录父亲节点,方便在找到节点后输出路径。
扩展:
代码:
// bfs
// 记录所有path路径
List<List<Integer>> res = new ArrayList<>();
// path:找到预期节点后向上父节点寻找(树特点:一个节点只有一个父亲节点)
Map<TreeNode, TreeNode> map = new HashMap<>();
public List<List<Integer>> pathSum2(TreeNode root, int targetSum) {
if (root == null) {
return res;
}
// 这两个队列元素是一一对应的
// 记录层序遍历节点(BFS思想)
Queue<TreeNode> queue = new LinkedList<>();
// 记录当前层级所有节点的和
Queue<Integer> sumQueue = new LinkedList<>();
queue.offer(root);
sumQueue.offer(root.val);
map.put(root, null);
while (!queue.isEmpty()) {
int size = queue.size();
while (size > 0) {
TreeNode curr = queue.poll();
int val = sumQueue.poll();
if (curr.left == null && curr.right == null && val == targetSum) {
getPath(curr);
}
if (curr.left != null) {
queue.offer(curr.left);
sumQueue.offer(val + curr.left.val);
map.put(curr.left, curr);
}
if (curr.right != null) {
queue.offer(curr.right);
sumQueue.offer(val + curr.right.val);
map.put(curr.right, curr);
}
size--;
}
}
return res;
}
/**
* 寻找父亲节点路径
* @param leaf
*/
public void getPath(TreeNode leaf) {
List<Integer> list = new ArrayList<>();
while (leaf != null) {
list.add(leaf.val);
leaf = map.get(leaf);
}
Collections.reverse(list);
res.add(list);
}
// dfs
// 记录所有path路径
List<List<Integer>> res2 = new LinkedList<>();
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
dfs(root, targetSum, new LinkedList());
return res2;
}
public void dfs(TreeNode root, int sum, LinkedList path) {
if (root == null) {
return;
}
sum -= root.val;
path.offer(root.val);
if (root.left == null && root.right == null && sum == 0) {
res2.add(new LinkedList<>(path));
}
dfs(root.left, sum, path);
dfs(root.right, sum, path);
path.pollLast();
}
437-路径总和 III
题意分析:
- 输出和为 sum 的路径,该路径不一定包含根节点或叶子节点,只有和为 sum 的节点都可以,且不跨越根节点。
解题思路:
- 结合路径总和II 解法,对所有节点递归调用 II 的逻辑,退出条件为 sum=0 即可。
注意点:
- 递归的退出条件。
扩展:
代码:
// 记录所有path路径
List<List<Integer>> res = new LinkedList<>();
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
if (root == null) {
return res;
}
dfs(root, targetSum, new LinkedList<>());
// 由于总和不一定包括根节点,对左右孩子节点也要递归遍历
pathSum(root.left, targetSum);
pathSum(root.right, targetSum);
return res;
}
public void dfs(TreeNode root, int sum, LinkedList path) {
if (root == null) {
return;
}
sum -= root.val;
path.offer(root.val);
// 和路径总和II 问题的区别是这里不用判断节点是否是叶子节点
if (sum == 0) {
res.add(new LinkedList<>(path));
}
dfs(root.left, sum, path);
dfs(root.right, sum, path);
path.pollLast();
}
687-最长同值路径(?)
题意分析:
- 树中最长的包含相同 val 的节点,路径可以包含根节点。
解题思路:
- dfs 后序遍历,计算每个节点的左右最大同值数量。
注意点:
扩展:
代码:
int res = 0;
public int longestUnivaluePath(TreeNode root) {
longestPath(root);
return res;
}
public int longestPath(TreeNode root){
if (root == null) {
return 0;
}
int left = longestPath(root.left);
int right = longestPath(root.right);
if (root.left != null && root.val == root.left.val) {
left++;
} else {
left = 0;
}
if (root.right != null && root.val == root.right.val) {
right++;
} else {
right = 0;
}
// 打印当前root节点的左右子树值都最好。
// System.out.println(root.val + ": left = " + left +"; right = " + right);
// 如果根节点的左右子树res都为1,则表示根节点和左右节点的值 val 相等。
res = Math.max(res, left + right);
// 返回左右子树的最长同值路径
return Math.max(left, right);
}
124-二叉树中的最大路径和(?)
题意分析:
- 二叉树中节点包含负数情况下最大的路径和,可以经过或不经过根节点。
解题思路:
- dfs 遍历分别计算每个节点的左右子树的最大路径和。
注意点:
扩展:
代码:
int maxSum = Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) {
maxPath(root);
return maxSum;
}
public int maxPath(TreeNode root) {
if (root == null) {
return 0;
}
// 如果某个路径小于0,则改路径是无增益路径
int left = Math.max(maxPath(root.left), 0);
int right = Math.max(maxPath(root.right), 0);
System.out.println(root.val + ": left = " + left +"; right = " + right);
maxSum = Math.max(maxSum, left + right + root.val);
return root.val + Math.max(left, right);
}
988-从叶节点开始的最小字符串
题意分析:
- 从所有叶子节点到根节点的路径中,找到字符串值最小的字符串。
解题思路:
- 类似 路径总和II 问题思路,找到所有路径,比较路径字符串大小。
注意点:
扩展:
代码:
String maxStr = "~";
public String smallestFromLeaf(TreeNode root) {
dfs(root, new StringBuilder());
return maxStr;
}
public void dfs(TreeNode root, StringBuilder path) {
if (root == null) {
return;
}
path.append((char)('a' + root.val));
if (root.left == null && root.right == null){
path.reverse();
String tmp = path.toString();
path.reverse();
if (tmp.compareTo(maxStr) < 0 ) {
maxStr = tmp;
}
}
dfs(root.left, path);
dfs(root.right, path);
// 回溯,删除 path 中最后一个节点,类似 路径总和II 问题思路
path.deleteCharAt(path.length() - 1);
}
前缀树
208-实现前缀树
题意分析:
- 前缀树的新增、查找和前缀判断功能。
解题思路:
- 定义前缀树,清楚前缀树的属性功能实现就相对容易。
注意点:
- search() 方法实现的return逻辑,需要判断字符是否是前缀的特殊情况。
扩展:
代码:
public class TreeTrieImpl_208 {
TrieNode root;
class TrieNode {
int pass;
int end;
TrieNode[] childs;
public TrieNode() {
pass = 0;
end = 0;
childs = new TrieNode[26];
}
}
public TreeTrieImpl_208() {
root = new TrieNode();
}
public void insert(String word) {
TrieNode node = root;
int idx;
char[] chs = word.toCharArray();
for (int i = 0; i < chs.length; i++) {
idx = chs[i] - 'a';
if (node.childs[idx] == null) {
node.childs[idx] = new TrieNode();
}
node.childs[idx].pass++;
node = node.childs[idx];
}
node.end++;
}
public boolean search(String word) {
TrieNode node = root;
int idx;
char[] chs = word.toCharArray();
for (int i = 0; i < chs.length; i++) {
idx = chs[i] - 'a';
if (node.childs[idx] == null) {
return false;
}
node = node.childs[idx];
}
// 不能直接返回ture,比如'apple'串中搜索'app',要判断是否结束字符
return node.end != 0;
}
public boolean startsWith(String prefix) {
TrieNode node = root;
int idx;
char[] chs = prefix.toCharArray();
for (int i = 0; i < chs.length; i++) {
idx = chs[i] - 'a';
if (node.childs[idx] == null) {
return false;
}
node = node.childs[idx];
}
return true;
}
public static void main(String[] args) {
TreeTrieImpl_208 treeTrieImpl208 = new TreeTrieImpl_208();
treeTrieImpl208.insert("apple");
System.out.println(treeTrieImpl208.search("apple")); // 返回 True
System.out.println(treeTrieImpl208.search("app")); // 返回 False
System.out.println(treeTrieImpl208.startsWith("app")); // 返回 True
treeTrieImpl208.insert("app");
System.out.println(treeTrieImpl208.search("app")); // 返回 True
}
}
648-单词替换
题意分析:
- 如果一个单词在前缀词字典中存在前缀词,则用前缀词替换。
解题思路:
- 选择字典还是句子作为前缀树是关键,然后判断前缀是否存在。
注意点:
扩展:
代码:
public class WordReplace_648 {
TrieNode root = new TrieNode();
public void insert(String word) {
TrieNode node = root;
int idx;
char[] chs = word.toCharArray();
for (int i = 0; i < chs.length; i++) {
idx = chs[i] - 'a';
if (node.childs[idx] == null) {
node.childs[idx] = new TrieNode();
}
node.pass++;
node = node.childs[idx];
}
node.end++;
}
public String shortestPrefix(String word) {
TrieNode node = root;
int idx;
char[] chs = word.toCharArray();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < chs.length; i++) {
if (node.end != 0) { // 找到了存在的前缀
break;
}
idx = chs[i] - 'a';
if (node.childs[idx] == null) {
return "";
}
sb.append(chs[i]);
node = node.childs[idx];
}
return sb.toString();
}
public String replaceWords(List<String> dictionary, String sentence) {
String[] strs = sentence.split(" ");
// 将 dictionary 中单词构建成前缀树
for (int i = 0; i < dictionary.size(); i++) {
insert(dictionary.get(i));
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < strs.length; i++) {
String prefix = shortestPrefix(strs[i]);
if (!prefix.isEmpty()) {
sb.append(prefix);
} else {
sb.append(strs[i]);
}
if (i != strs.length -1) {
sb.append(" ");
}
}
return sb.toString();
}
public static void main(String[] args) {
WordReplace_648 obj = new WordReplace_648();
List<String> dictionary = new ArrayList<>(Arrays.asList("cat","bat","rat"));
String sentence = "the cattle was rattled by the battery";
System.out.println(obj.replaceWords(dictionary, sentence));
}
}
211-添加与搜索
题意分析:
- 前缀树的应用,向前缀树中添加字符串,然后查询prefix前缀是否存在,也存在模式匹配的前缀。
解题思路:
- 构建前缀树,然后采用 dfs + 回溯 方式遍历。
注意点:
- dfs 遍历添加参数 pattern 和 idx 表示递归体在向前遍历。这种递归走向还不是特别容易理解。
扩展:
代码:
public class WordDictionary {
TrieNode root;
public WordDictionary() {
root = new TrieNode();
}
public void addWord(String word) {
if (word == null) {
return;
}
char[] chs = word.toCharArray();
TrieNode node = root;
node.pass++;
int idx;
// 遍历每一个字符,两种选择:有路径或者没有路径
for (int i = 0; i < chs.length; i++) {
idx = chs[i] - 'a';
if (node.childs[idx] == null) {
// 没有路径则新建路径
node.childs[idx] = new TrieNode();
}
node = node.childs[idx];
node.pass++;
}
node.end++;
}
public boolean search(String word) {
return hasKeyWithPattern(root, word, 0);
}
private boolean hasKeyWithPattern(TrieNode node, String pattern, int idx) {
if (node == null) {
return false;
}
if (idx == pattern.length()) {
return node.end != 0;
}
char ch = pattern.charAt(idx);
if (ch == '.') {
// '.' 符号则遍历所有子树
for (int i = 0; i < node.childs.length; i++) {
if (node.childs[i] != null) {
if (hasKeyWithPattern(node.childs[i], pattern, idx + 1))
return true;
}
}
} else {
return hasKeyWithPattern(node.childs[ch - 'a'], pattern, idx + 1);
}
return false;
}
public static void main(String[] args) {
WordDictionary wordDictionary = new WordDictionary();
wordDictionary.addWord("at");
wordDictionary.addWord("and");
wordDictionary.addWord("an");
wordDictionary.addWord("add");
System.out.println(wordDictionary.search("a")); // return False
System.out.println(wordDictionary.search(".at")); // return False
wordDictionary.addWord("bat");
System.out.println(wordDictionary.search(".at")); // return true
}
}
677-键值映射
题意分析:
- 首先是判断前缀prefix是否是存在字符串的前缀,如果是,求所有以 prefix 为前缀的字符的 val 的和。
解题思路:
- 熟悉前缀树的基本操作就比较容易,首先构建前缀树,然后判断前缀 prefix 是否是合法的(即 getNode 判断前缀是否存在对应的路径节点),最后从 prefix 的路径结束节点开始 dfs 遍历,求所有叶子节点的 val 和。
注意点:
扩展:
代码:
public class TrieMapSum_677 {
TrieNode root;
public TrieMapSum_677() {
root = new TrieNode();
}
public void insert(String key, int val) {
TrieNode node = root;
node.pass++;
int idx;
char[] chs = key.toCharArray();
for (int i = 0; i < chs.length; i++) {
idx = chs[i] - 'a';
if (node.childs[idx] == null) {
node.childs[idx] = new TrieNode();
}
node = node.childs[idx];
node.pass++;
}
node.end++;
node.val = val;
}
int sum = 0;
public int sum(String prefix) {
sum = 0;
TrieNode node = getNode(prefix);
if (node == null) {
return 0;
}
// 以node为根节点递归遍历,求其子树下所有叶子节点的和
dfs(node);
return sum;
}
public void dfs(TrieNode node) {
if (node == null) {
return;
}
if (node.end != 0) {
sum += node.val;
}
// 树的先序遍历。(多叉树)
for (int i = 0; i < node.childs.length; i++) {
if (node.childs[i] != null) {
dfs(node.childs[i]);
}
}
}
public TrieNode getNode(String prefix) {
TrieNode node = root;
int idx;
char[] chs = prefix.toCharArray();
for (int i = 0; i < chs.length; i++) {
idx = chs[i] - 'a';
if (node.childs[idx] == null) {
return null;
}
node = node.childs[idx];
}
return node;
}
public static void main(String[] args) {
TrieMapSum_677 mapSum = new TrieMapSum_677();
mapSum.insert("apple", 3);
System.out.println(mapSum.sum("ap")); // return 3 (apple = 3)
mapSum.insert("app", 2);
System.out.println(mapSum.sum("ap")); // return 5 (apple + app = 3 + 2 = 5)
}
}
未分类
543-二叉树的直径
题意分析:
- 直径定义:无非就是判断根节点的左右子树深度,两者和即为直径。
解题思路:
- 后序遍历,计算每个节点的左右子树深度。
注意点:
扩展:
代码:
int res = 0;
public int diameterOfBinaryTree(TreeNode root) {
diameter(root);
return res;
}
public int diameter(TreeNode root) {
if (root == null) {
return 0;
}
int left = diameter(root.left);
int right = diameter(root.right);
if (root.left != null) {
left++;
} else {
left = 0;
}
if (root.right != null) {
right++;
} else {
right = 0;
}
System.out.println(root.val + ": left = " + left +"; right = " + right);
// 统计最长路径。如果是统计节点: left + right + 1
res = Math.max(res, left + right);
return Math.max(left, right);
}
101-对称二叉树
题意分析:
- 判断 p.left 和 q.right 元素是否是一致的
解题思路:
- bfs 遍历。每次入队两个元素,即对称的两个节点元素,判断是否对称。
- dfs 遍历。递归判断两个节点。
注意点:
扩展:
代码:
/**
* bfs 遍历
* @param root
* @return
*/
public boolean isSymmetric(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
queue.offer(root);
while (!queue.isEmpty()) {
TreeNode p = queue.poll();
TreeNode q = queue.poll();
if (p == null && q == null) {
continue;
}
if (p == null || q == null || p.val != q.val) {
return false;
}
queue.offer(p.left);
queue.offer(q.right);
queue.offer(p.right);
queue.offer(q.left);
}
return true;
}
/**
* dfs 遍历
* @param root
* @return
*/
public boolean isSymmetric2(TreeNode root) {
return check(root, root);
}
private boolean check(TreeNode p, TreeNode q) {
if (p == null && q == null) {
return true;
}
if (p == null || q == null)
return false;
return (p.val == q.val) && check(p.left, q.right) && check(p.right, q.left);
}
104-二叉树的最大深度
题意分析:
- 树的最大深度。
解题思路:
- bfs 最直观,遍历每一层的节点。
- dfs 后序(自底向上)遍历记录每个节点的左右最大深度。
注意点:
扩展:
代码:
/**
* 树的深度优先遍历
* @param root
* @return
*/
public int maxDepth(TreeNode root) {
if (root == null) {
return 0;
}
int leftDepth = maxDepth(root.left);
int rightDepth = maxDepth(root.right);
return Math.max(leftDepth, rightDepth) + 1;
}
public int maxDepth2(TreeNode root) {
if (root == null) {
return 0;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
int depth = 0;
while (!queue.isEmpty()) {
// size 记录每一层节点的数量,一层节点出队列完毕后深度+1
int size = queue.size();
while (size > 0 ) {
TreeNode node = queue.poll();
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
size--;
}
depth++;
}
return depth;
}
111-二叉树的最小深度
题意分析:
- 和最大深度差不多。
解题思路:
- bfs 遍历最简单,遇到叶子节点直接返回深度。
注意点:
扩展:
代码:
int res = Integer.MAX_VALUE;
public int minDepth(TreeNode root) {
if (root == null) {
return 0;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
int depth = 0;
while (!queue.isEmpty()) {
// size 记录每一层节点的数量,一层节点出队列完毕后深度+1
int size = queue.size();
while (size > 0 ) {
TreeNode node = queue.poll();
if (node.left == null && node.right == null) {
return ++depth;
}
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
size--;
}
depth++;
}
return depth;
}
987-二叉树的垂序遍历
题意分析:
- 根据每个节点 x/y 值对树节点进行遍历。
解题思路:
- 逻辑思路比较清晰。
- 先遍历树,计算每个节点的 x 和 y 值。
- 然后对所有节点排序,自定义排序器,分别按 y、x、val 升序遍历。
- 分组输出节点元素。
注意点:
扩展:
代码:
/**
* 题目考查的知识点:
* 1、二叉树的遍历;
* 2、Java 比较器的实现;
* 3、数组/List 的遍历技巧。
* List[1,2,3,3,3,4,4,5] = 统计每个单词出现的次数,并放在不同集合中。[[1],[2],[3,3,3],[4,4],[5]]
*/
public class VerticalTraversal_987 {
List<VerticalNode> nodeList = new ArrayList<>();
public List<List<Integer>> verticalTraversal(TreeNode root) {
// 遍历树节点,获得每个节点的 [x, y , val] 组值
dfs(root, 0, 0);
// System.out.println("----------排序前--------");
// for (VerticalNode node : nodeList) {
// System.out.println(node.val + ": " + node.x + ": " + node.y);
// }
// 排序,先根据列 y 升序排序,如果 y 相同,则按照 val 升序排序
Collections.sort(nodeList, new Comparator<VerticalNode>() {
@Override
public int compare(VerticalNode o1, VerticalNode o2) {
if (o1.y != o2.y) {
return o1.y - o2.y; // 按 y 升序
} else if (o1.x != o2.x) {
return o1.x - o2.x; // 按 x 升序
} else {
return o1.val - o2.val;
}
}
});
System.out.println("----------排序后--------");
for (VerticalNode node : nodeList) {
System.out.println(node.val + ": " + node.x + ": " + node.y);
}
// 排序后输出结果
List<List<Integer>> retList = new ArrayList<>();
List<Integer> list = new ArrayList<>();
int last_y = Integer.MIN_VALUE;
// 记录有多少组不同 y 元素
int size = -1;
// 此时遍历可忽略 行x 的影响,因为 x 是有序的,List 也是有序的,根据 y 遍历输出 val 的值即可。
for (VerticalNode node : nodeList) {
if (node.y != last_y) {
size++;
last_y = node.y;
list.add(node.val);
retList.add(list);
list = new ArrayList<>();
} else {
retList.get(size).add(node.val);
}
}
System.out.println(retList);
return retList;
}
public void dfs(TreeNode root, int x, int y) {
if (root == null) {
return;
}
nodeList.add(new VerticalNode(root.val, x, y));
// 对左右子树递归遍历
dfs(root.left, x+1, y-1);
dfs(root.right, x+1, y+1);
}
/**
* 1
* 2 3
* 4 5
*/
public static void main(String[] args) {
VerticalTraversal_987 obj = new VerticalTraversal_987();
System.out.println(obj.verticalTraversal(Utils.createTree2()));
}
class VerticalNode {
int val;
int x;
int y;
public VerticalNode(int val, int x, int y) {
this.val = val;
this.x = x;
this.y = y;
}
}
}
模版
题意分析:
解题思路:
注意点:
扩展:
代码: