@[TOP](leetcode 树)
前序遍历:父节点 -> 左子节点 -> 右子节点
中序遍历:左子节点 -> 父节点 -> 右子节点
后序遍历:左子节点 -> 右子节点 -> 父节点
https://leetcode-cn.com/problems/binary-tree-preorder-traversal/
输入: [1,null,2,3]
1
\
2
/
3
输出: [1,2,3]
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while(!stack.isEmpty()) {
TreeNode node = stack.pop();
if(node == null) continue;
list.add(node.val);
stack.push(node.right);
stack.push(node.left);
}
return list;
}
}
前序遍历为 root -> left -> right,后序遍历为 left -> right -> root。可以修改前序遍历成为 root -> right -> left,那么这个顺序就和后序遍历正好相反。
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while(!stack.isEmpty()){
TreeNode node = stack.pop();
if(node == null) continue;
list.add(node.val);
stack.push(node.left);
stack.push(node.right);
}
Collections.reverse(list); //反转List集合中的元素,返回值为void
return list;
}
}
力扣
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
if(root == null) return list;
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while(cur != null || !stack.isEmpty()){
while(cur != null){
stack.push(cur);
cur = cur.left;
}
TreeNode node = stack.pop();
list.add(node.val);
cur = node.right;
}
return list;
}
}
public int maxDepth(TreeNode root) {
if (root == null) return 0;
return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
}
一棵高度平衡二叉树定义为:
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。
class Solution {
private boolean result = true;
public boolean isBalanced(TreeNode root) {
maxDepth(root);
return result;
}
public int maxDepth(TreeNode root){
if(root == null) return 0;
int leftDepth = maxDepth(root.left);
int rightDepth = maxDepth(root.right);
if(Math.abs(leftDepth - rightDepth) > 1) result = false;
return Math.max(leftDepth, rightDepth) + 1;
}
}
一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。
两结点之间的路径长度是以它们之间边的数目表示。
class Solution {
private int max = 0;
public int diameterOfBinaryTree(TreeNode root) {
maxDepth(root);
return max;
}
public int maxDepth(TreeNode root){
if(root == null) return 0;
int leftDepth = maxDepth(root.left);
int rightDepth = maxDepth(root.right);
max = Math.max(max, (leftDepth + rightDepth));
return Math.max(leftDepth,rightDepth) + 1;
}
}
class Solution {
public TreeNode invertTree(TreeNode root) {
if(root == null) return null;
TreeNode left = root.left; //后面的操作会改变 left 指针,因此先保存下来
root.left = invertTree(root.right);
root.right = invertTree(left);
return root;
}
}
力扣
如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。
class Solution {
public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
if(t1 == null && t2 == null) return null;
if(t1 == null) return t2;
if(t2 == null) return t1;
TreeNode root = new TreeNode(t1.val + t2.val);
root.left = mergeTrees(t1.left, t2.left);
root.right = mergeTrees(t1.right, t2.right);
return root;
}
}
力扣
要求根节点到叶子节点
class Solution {
public boolean hasPathSum(TreeNode root, int sum) {
if(root == null) return false;
if(root.left == null && root.right == null && root.val == sum){
//叶子节点
return true;
}
return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val);
}
}
力扣
不要求从根节点到叶子节点,但方向向下(父 -> 子)
class Solution {
public int pathSum(TreeNode root, int sum) {
if(root == null) return 0;
int res = pathSumWithRoot(root, sum) + pathSum(root.left, sum) + pathSum(root.right, sum);
return res;
}
public int pathSumWithRoot(TreeNode root, int sum){
int count = 0;
if(root == null) return 0;
if(root.val == sum) count++;
count += pathSumWithRoot(root.left, sum - root.val) + pathSumWithRoot(root.right, sum - root.val);
return count;
}
}
力扣
给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树。s 的一个子树包括 s 的一个节点和这个节点的所有子孙。s 也可以看做它自身的一棵子树。
class Solution {
public boolean isSubtree(TreeNode s, TreeNode t) {
if(s == null) return false;
return isSubtreeWithRoot(s,t) || isSubtree(s.left, t) || isSubtree(s.right, t); //t是子树
}
public boolean isSubtreeWithRoot(TreeNode s, TreeNode t){
if(s == null && t == null) return true;
if(s == null || t == null) return false;
if(s.val != t.val) return false; //不相等立马退出
return isSubtreeWithRoot(s.left, t.left) && isSubtreeWithRoot(s.right, t.right);
}
}
力扣
给定一个二叉树,检查它是否是镜像对称的。
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root == null) return true;
return isSymmetric(root.left, root.right);
}
public boolean isSymmetric(TreeNode t1, TreeNode t2){
if(t1 == null && t2 == null) return true;
if(t1 == null || t2 == null) return false;
if(t1.val != t2.val) return false;
return isSymmetric(t1.left,t2.right) && isSymmetric(t1.right, t2.left);
}
}
力扣
class Solution {
public int minDepth(TreeNode root) {
if(root == null) return 0;
int left = minDepth(root.left);
int right = minDepth(root.right);
if(left == 0 || right == 0) return (left + right + 1); //得到分支的深度
return Math.min(left, right) + 1;
}
}
力扣
class Solution {
public int sumOfLeftLeaves(TreeNode root) {
if(root == null) return 0;
if(isLeaf(root.left)) return root.left.val + sumOfLeftLeaves(root.right);
return sumOfLeftLeaves(root.left) + sumOfLeftLeaves(root.right);
}
public boolean isLeaf(TreeNode node){
if(node == null) return false;
return node.left == null && node.right == null;
}
}
力扣
给定一个二叉树,找到最长的路径,这个路径中的每个节点具有相同值。 这条路径可以经过也可以不经过根节点。
class Solution {
private int sum;
public int longestUnivaluePath(TreeNode root) {
dfs(root);
return sum;
}
private int dfs(TreeNode root){
if(root == null) return 0;
int left = dfs(root.left);
int right = dfs(root.right);
int leftPath = root.left != null && root.left.val == root.val ? left + 1 : 0;
int rightPath = root.right != null && root.right.val == root.val ? right + 1 : 0;
sum = Math.max(sum, leftPath + rightPath);
return Math.max(leftPath, rightPath);
}
}
力扣
class Solution {
public int rob(TreeNode root) {
if(root == null) return 0;
int val1 = root.val;
if(root.left != null) val1 += rob(root.left.left) + rob(root.left.right);
if(root.right != null) val1 += rob(root.right.left) + rob(root.right.right);
int val2 = rob(root.left) + rob(root.right);
return Math.max(val1,val2);
}
}
力扣
根据题意可知根节点的值最小
class Solution {
public int findSecondMinimumValue(TreeNode root) {
return helper(root, root.val);
}
public int helper(TreeNode root, int minVal){
//叶子节点
if(root == null) return -1;
//如果当前结点值>根节点,那么不用再遍历它的子节点,直接返回该值
if(root.val > minVal) return root.val;
//否则,当前结点值==根节点,继续遍历,需要在两棵子树找目标值结点
int l = helper(root.left, minVal);
int r = helper(root.right, minVal);
//如果两棵子树均存在大于最小值的节点,那么返回较小的那一个
if(l != -1 && r !=-1) {
return Math.min(l,r);
}else {
//否则,其余情况均返回较大的那一个
return Math.max(l,r);
}
}
}
使用 BFS 进行层次遍历。不需要使用两个队列来分别存储当前层的节点和下一层的节点,因为在开始遍历一层的节点时,当前队列中的节点数就是当前层的节点数,只要控制遍历这么多节点数,就能保证这次遍历的都是当前层的节点。
inkedList中的poll()和pop():
当头结点为空的时候,两个函数的处理方式不同:poll()选择返回null,pop()选择抛出异常。
也可以说,两个函数虽然实现效果一样,但他们字面意义不同。
class Solution {
public List<Double> averageOfLevels(TreeNode root) {
List<Double> avg = new ArrayList<>();
if(root == null) return avg;
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while(!queue.isEmpty()){
int cnt = queue.size();
double sum = 0; //注意数据类型
for(int i = 0; i < cnt; i++){
TreeNode node = queue.poll();
sum += node.val;
if(node.left != null) queue.add(node.left);
if(node.right != null) queue.add(node.right);
}
avg.add(sum / cnt);
}
return avg;
}
}
力扣
class Solution {
public int findBottomLeftValue(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while(!queue.isEmpty()){
root = queue.poll();
if(root.right != null) queue.add(root.right);
if(root.left != null) queue.add(root.left);
}
return root.val;
}
}
二叉查找树(BST):根节点大于等于左子树所有节点,小于等于右子树所有节点。
二叉查找树中序遍历有序。
力扣
给定最小边界L 和最大边界 R。只保留值在 L ~ R 之间的节点
class Solution {
public TreeNode trimBST(TreeNode root, int L, int R) {
if(root == null) return null;
//当root过大则右子树全部修剪掉,遍历左子树,修改root
if(root.val > R) return trimBST(root.left, L, R);
//当root过小则左子树全部修剪掉,遍历右子树,修改root
if(root.val < L) return trimBST(root.right, L, R);
//处理在范围内的节点
root.left = trimBST(root.left, L, R);
root.right = trimBST(root.right, L, R);
return root;
}
}
力扣
中序遍历法
class Solution {
int count;
int val;
public int kthSmallest(TreeNode root, int k) {
Order(root, k);
return val;
}
public void Order(TreeNode root, int k){
if(root == null) return;
Order(root.left, k);
count++;
if(count == k){
val = root.val;
}
Order(root.right,k);
}
}
力扣
先遍历右子树
class Solution {
int sum;
public TreeNode convertBST(TreeNode root) {
rightFirst(root);
return root;
}
public void rightFirst(TreeNode root){
if(root == null) return;
rightFirst(root.right);
sum += root.val;
root.val = sum;
rightFirst(root.left);
}
}
力扣
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root.val > p.val && root.val > q.val) return lowestCommonAncestor(root.left, p, q);
if(root.val < p.val && root.val < q.val) return lowestCommonAncestor(root.right, p, q);
return root;
}
}
力扣
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null || root == p || root == q) return root;
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
return left == null ? right : right == null ? left : root;
}
}
力扣
由于平衡二叉树是左右两个子树的高度差的绝对值不超过 1。因此一种简单的方法是选择中点作为根节点,根节点左侧的作为左子树,右侧的作为右子树即可。原因很简单,这样分配可以保证左右子树的节点数目差不超过 1。因此高度差自然也不会超过 1 了。
上面的操作同时也满足了二叉搜索树,原因就是题目给的数组是有序的。
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
return toBST(nums, 0, nums.length - 1);
}
public TreeNode toBST(int[] nums, int lIdx, int hIdx){
if(lIdx > hIdx) return null;
int mIdx = lIdx + (hIdx - lIdx) / 2;
TreeNode root = new TreeNode(nums[mIdx]);
root.left = toBST(nums, lIdx, mIdx - 1);
root.right = toBST(nums, mIdx + 1, hIdx);
return root;
}
}
力扣
思路 :找到中点,只需要使用经典的快慢指针即可。同时为了防止环的出现, 需要斩断指向 mid 的 next 指针,因此需要记录中点前的一个节点,这只需要用一个变量 pre 记录即可。
时间复杂度:由于每个节点最多被访问一次,因此总的时间复杂度为 O(N),其中 N 为链表长度。
空间复杂度:由于使用了递归,这里的空间复杂度的瓶颈在栈空间,因此空间复杂度为O(h),其中h 为树的高度。同时由于是平衡二叉树,因此 h 就是 logN。
class Solution {
public TreeNode sortedListToBST(ListNode head) {
if(head == null) return null;
if(head.next == null) return new TreeNode(head.val);
ListNode pre = preMid(head);
ListNode mid = pre.next;
pre.next = null;
TreeNode t = new TreeNode(mid.val);
t.left = sortedListToBST(head);
t.right = sortedListToBST(mid.next);
return t;
}
public ListNode preMid(ListNode head){
ListNode pre = head;
ListNode slow = head;
ListNode fast = head.next;
while(fast != null && fast.next != null){
pre = slow;
slow = slow.next;
fast = fast.next.next;
}
return pre;
}
}
力扣
使用中序遍历得到有序数组之后,再利用双指针对数组进行查找。
应该注意到,这一题不能用分别在左右子树两部分来处理这种思想,因为两个待求的节点可能分别在左右子树中。
class Solution {
public boolean findTarget(TreeNode root, int k) {
List<Integer> nums = new ArrayList<>();
inOrder(root, nums);
int i = 0, j = nums.size() - 1;
while(i < j){
int sum = nums.get(i) + nums.get(j);
if(sum == k) return true;
if(sum < k) i++;
else j--;
}
return false;
}
public void inOrder(TreeNode root, List<Integer> nums){
if(root == null) return;
inOrder(root.left, nums);
nums.add(root.val);
inOrder(root.right,nums);
}
}
力扣
利用二叉查找树的中序遍历为升序的性质,计算中序遍历中临近的两个节点之差的绝对值,取最小值。
class Solution {
private int minDiff = Integer.MAX_VALUE;
TreeNode preNode = null;
public int getMinimumDifference(TreeNode root) {
inOrder(root);
return minDiff;
}
public void inOrder(TreeNode root){
if(root == null) return;
inOrder(root.left);
if(preNode != null) minDiff = Math.min(minDiff, root.val - preNode.val);
preNode = root;
inOrder(root.right);
}
}
力扣
答案可能不止一个,也就是有多个值出现的次数一样多。
class Solution {
private TreeNode preNode = null;
private int maxCount = 1;
private int count = 1;
public int[] findMode(TreeNode root) {
List<Integer> maxCntNum = new ArrayList<>();
inOrder(root, maxCntNum);
int index = 0;
int[] res = new int[maxCntNum.size()];
for(int num : maxCntNum){
res[index++] = num;
}
return res;
}
public void inOrder(TreeNode root, List<Integer> maxCntNum){
if(root == null) return;
inOrder(root.left, maxCntNum);
if(preNode != null) {
if(preNode.val == root.val){
count++;
}else{
count = 1;
}
}
if(count > maxCount){
maxCount = count;
maxCntNum.clear();
maxCntNum.add(root.val);
}else if(count == maxCount){
maxCntNum.add(root.val);
}
preNode = root;
inOrder(root.right, maxCntNum);
}
}
Trie,又称前缀树或字典树,用于判断字符串是否存在或者是否具有某种字符串前缀。
参考下面的文章学习字典树
https://mp.weixin.qq.com/s/uDar0F7x9w5F3sHOB5tIDA
力扣
class Trie {
Trie[] next = new Trie[26]; //每个节点都是一个包含26个字母的数组
boolean isEnd = false; //是否为最后一个字母
/** Initialize your data structure here. */
public Trie() {
}
/** Inserts a word into the trie. */
public void insert(String word) {
Trie curr = this; //当前节点,为空节点
for(char c : word.toCharArray()){
if(curr.next[c - 'a'] == null){
//若不存在字母创建Trie
curr.next[c - 'a'] = new Trie();
}
curr = curr.next[c - 'a'];//移动指针
}
curr.isEnd = true; //设置最后一个加入的字母为最后一个
}
/** Returns if the word is in the trie. */
public boolean search(String word) {
Trie curr = this;
for(char c : word.toCharArray()){
if(curr.next[c - 'a'] == null) return false;
curr = curr.next[c - 'a'];
}
return curr.isEnd; //这个词的最后一个字母必须是搜索的最后一个字母,否则是前缀
}
/** Returns if there is any word in the trie that starts with the given prefix. */
public boolean startsWith(String prefix) {
Trie curr = this;
for(char c : prefix.toCharArray()){
if(curr.next[c - 'a'] == null) return false;
curr = curr.next[c - 'a'];
}
return true;
}
}
力扣
class MapSum {
//创建字典树节点类
private class Node{
public int value;
public HashMap<Character, Node> next; //使用map存储Node信息,保存value,以便后续的求和操作
public Node(){
//无参构造,传值为0
this(0);
}
public Node(int value){
this.value = value;
this.next = new HashMap<>();
}
}
private Node root; //由于插入单词时需要再同一个跟节点上进行,所以在外部初始化一个root
/** Initialize your data structure here. */
public MapSum() {
root = new Node();
}
public void insert(String key, int val) {
Node curr = root;
for(char c : key.toCharArray()){
if(curr.next.get(c) == null){
curr.next.put(c, new Node());
}
curr = curr.next.get(c);
}
curr.value = val;
}
public int sum(String prefix) {
Node curr = root;
for(char c : prefix.toCharArray()){
if(curr.next.get(c) == null) return 0;
curr = curr.next.get(c);
}
return sum(curr);
}
public int sum(Node node){
int sum = node.value;
for(char c : node.next.keySet()){
sum += sum(node.next.get(c)); //递归获得每个节点的值并相加
}
return sum;
}
}