数据结构
1、二叉树
1.1、二叉树的遍历方式
leetcode 94、144、145二叉树的前、中、后序遍历
——递归遍历
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<Integer>();
preorder(root, result);
return result;
}
public void preorder(TreeNode root, List<Integer> result) {
if (root == null) {
return;
}
result.add(root.val);
preorder(root.left, result);
preorder(root.right, result);
}
}
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
inorder(root, res);
return res;
}
public void inorder(TreeNode root, List<Integer> list) {
if (root == null) {
return;
}
inorder(root.left, list);
list.add(root.val);
inorder(root.right, list);
}
}
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
postorder(root, res);
return res;
}
public void postorder(TreeNode root, List<Integer> list) {
if (root == null) {
return;
}
postorder(root.left, list);
postorder(root.right, list);
list.add(root.val);
}
}
class HereNode {
int no;
String name;
HereNode left;
HereNode right;
public HereNode(int no, String name) {
this.no = no;
this.name = name;
}
public void preOrder (){
if(this.left!=null){
this.left.preOrder();
}
System.out.println(this);
if(this.right!=null){
this.right.preOrder();
}
}
}
——迭代遍历
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> list = new LinkedList<>();
Stack<TreeNode> stack = new Stack<>();
TreeNode node = root;
while(!stack.isEmpty() || node != null) {
while(node!=null){
list.add(node.val);
stack.push(node);
node = node.left;
}
node=stack.pop();
node=node.right;
}
return list;
}
}
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> list = new LinkedList<>();
Stack<TreeNode> stack = new Stack<>();
TreeNode node = root;
while(!stack.isEmpty() || node != null) {
while(node!=null){
stack.push(node);
node = node.left;
}
node=stack.pop();
list.add(node.val);
node=node.right;
}
return list;
}
}
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> list = new LinkedList<>();
Stack<TreeNode> stack = new Stack<>();
TreeNode node = root;
TreeNode pre = null;
while(!stack.isEmpty() || node != null) {
while(node!=null){
stack.push(node);
node = node.left;
}
node=stack.pop();
if(node.right == null||node.right==pre){
list.add(node.val);
pre=node;
node=null;
}
else{
stack.push(node);
node=node.right;
}
}
return list;
}
}
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> list = new LinkedList<>();
Stack<TreeNode> stack = new Stack<>();
TreeNode node = root;
while(!stack.isEmpty() || node != null) {
while(node!=null){
list.add(node.val);
stack.push(node);
node = node.right;
}
node=stack.pop();
node=node.left;
}
Collections.reverse(list);
return list;
}
}
1.2、二叉树的层序遍历
——leetcode 102.二叉树的层序遍历
DFS(深度优先搜索)和 BFS(广度优先搜索)
让我们先看看在二叉树上进行 DFS 遍历和 BFS 遍历的代码比较。
DFS 遍历使用递归:
void dfs(TreeNode root) {
if (root == null) {
return;
}
dfs(root.left);
dfs(root.right);
}
BFS 遍历使用队列数据结构:
void bfs(TreeNode root) {
Queue<TreeNode> queue = new ArrayDeque<>();
queue.add(root);
while (!queue.isEmpty()) {
TreeNode node = queue.poll();
if (node.left != null) {
queue.add(node.left);
}
if (node.right != null) {
queue.add(node.right);
}
}
}
? 只是比较两段代码的话,最直观的感受就是:DFS 遍历的代码比 BFS 简洁太多了!这是因为递归的方式隐含地使用了系统的 栈,我们不需要自己维护一个数据结构。如果只是简单地将二叉树遍历一遍,那么 DFS 显然是更方便的选择。
? 虽然 DFS 与 BFS 都是将二叉树的所有结点遍历了一遍,但它们遍历结点的顺序不同。
BFS 的应用一:层序遍历
BFS 的层序遍历应用就是本题了:
给定一个二叉树,返回其按层序遍历得到的节点值。 层序遍历即逐层地、从左到右访问所有结点。
什么是层序遍历呢?简单来说,层序遍历就是把二叉树分层,然后每一层从左到右遍历:
乍一看来,这个遍历顺序和 BFS 是一样的,我们可以直接用 BFS 得出层序遍历结果。然而,层序遍历要求的输入结果和 BFS 是不同的。层序遍历要求我们区分每一层,也就是返回一个二维数组。而 BFS 的遍历结果是一个一维数组,无法区分每一层。
由于我们无法区分队列中的结点来自哪一层,因此,我们需要稍微修改一下代码,在每一层遍历开始前,先记录队列中的结点数量 n(也就是这一层的结点数量),然后一口气处理完这一层的 n 个结点。
void bfs(TreeNode root) {
Queue<TreeNode> queue = new ArrayDeque<>();
queue.add(root);
while (!queue.isEmpty()) {
int n = queue.size();
for (int i = 0; i < n; i++) {
TreeNode node = queue.poll();
if (node.left != null) {
queue.add(node.left);
}
if (node.right != null) {
queue.add(node.right);
}
}
}
}
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> list = new LinkedList<>();
Queue<TreeNode> queue = new LinkedList<>();
if(root!=null){
queue.add(root);
}
while(!queue.isEmpty()){
int n = queue.size();
List<Integer> level = new LinkedList<>();
for(int i=0;i<n;i++){
TreeNode node = queue.poll();
level.add(node.val);
if(node.left!=null){
queue.add(node.left);
}
if(node.right!=null){
queue.add(node.right);
}
}
list.add(level);
}
return list;
}
}
——leetcode 107.二叉树的层序遍历 II
class Solution {
public List<List<Integer>> levelOrderBottom(TreeNode root) {
List<List<Integer>> list = new LinkedList<>();
Stack<List<Integer>> stack = new Stack<>();
Queue<TreeNode> queue = new LinkedList<>();
if(root!=null){
queue.add(root);
}
while(!queue.isEmpty()){
int n = queue.size();
List<Integer> level = new LinkedList<>();
for(int i = 0;i<n;i++){
TreeNode node = queue.poll();
level.add(node.val);
if(node.left!=null){
queue.add(node.left);
}
if(node.right!=null){
queue.add(node.right);
}
}
stack.push(level);
}
while(!stack.isEmpty()){
list.add(stack.pop());
}
return list;
}
}
——leetcode 199. 二叉树的右视图
BFS解法的思路: (常规解法)利用 BFS 进行层次遍历,记录下每层的最后一个元素。
class Solution {
public List<Integer> rightSideView(TreeNode root) {
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();
for (int i = 0; i < size; i++) {
TreeNode node = queue.poll();
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
if (i == size - 1) {
res.add(node.val);
}
}
}
return res;
}
}
DFS解法的思路: 我们按照 「根结点 -> 右子树 -> 左子树」 的顺序访问,就可以保证每层都是最先访问最右边的节点的。(与先序遍历 「根结点 -> 左子树 -> 右子树」 正好相反,先序遍历每层最先访问的是最左边的节点)
但是在解决这道题时,遇到了一个问题是该算法是层序遍历,我们必须考虑深度问题;而错误解法没有考虑[1,2,3,4]这种树型,偏向了单一子树如右子树;因此深度问题还需注意!
class Solution {
public List<Integer> rightSideView(TreeNode root) {
List<Integer> list = new LinkedList<>();
rightSide(root,list);
return list;
}
public void rightSide(TreeNode root,List list) {
if(root==null) return;
list.add(root.val);
if(root.right!=null) {
rightSide(root.right,list);
}
if(root.right==null&&root.left!=null) {
rightSide(root.left,list);
}
}
}
class Solution {
public List<Integer> rightSideView(TreeNode root) {
List<Integer> list = new LinkedList<>();
rightSide(root,list,0);
return list;
}
public void rightSide(TreeNode root,List list,int depth) {
if(root==null) return;
if(depth==list.size()){
list.add(root.val);
}
depth++;
rightSide(root.right,list,depth);
rightSide(root.left,list,depth);
}
}
——leetcode 637. 二叉树的层平均值
class Solution {
public List<Double> averageOfLevels(TreeNode root) {
List<Double> list = new LinkedList<>();
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while(!queue.isEmpty()) {
int n=queue.size();
double res = 0;
for(int i=0;i<n;i++) {
TreeNode node = queue.poll();
res += node.val;
if(node.left!=null) {
queue.add(node.left);
}
if(node.right!=null) {
queue.add(node.right);
}
}
list.add(res/n);
}
return list;
}
}
——leetcode 429.N叉树的层序遍历
题目:给定一个 N 叉树,返回其节点值的层序遍历。(即从左到右,逐层遍历)。
树的序列化输入是用层序遍历,每组子节点都由 null 值分隔(参见示例)。
输入:root = [1,null,2,3,4,5,null,null,6,7,null,8,null,9,10,null,null,11,null,12,null,13,null,null,14] 输出:[[1],[2,3,4,5],[6,7,8,9,10],[11,12,13],[14]]
class Solution {
public List<List<Integer>> levelOrder(Node root) {
List<List<Integer>> res = new LinkedList<>();
Queue<Node> queue = new LinkedList<>();
if(root==null) return res;
queue.add(root);
while(!queue.isEmpty()){
int n = queue.size();
List<Integer> list = new LinkedList<>();
for(int i = 0;i < n;i++) {
Node node = queue.poll();
list.add(node.val);
if(node.children!=null) {
for(Node child :node.children){
queue.add(child);
}
}
}
res.add(list);
}
return res;
}
}
——leetcode 515.在每个树行中找最大值
题目:给定一棵二叉树的根节点 root ,请找出该二叉树中每一层的最大值。
输入: root = [1,3,2,5,3,null,9] 输出: [1,3,9]
class Solution {
public List<Integer> largestValues(TreeNode root) {
List<Integer> list = new LinkedList<>();
Queue<TreeNode> queue = new LinkedList<>();
if(root == null) return list;
queue.add(root);
while(!queue.isEmpty()) {
int n = queue.size();
int res = queue.peek().val;
for(int i =0;i < n;i ++) {
TreeNode node = queue.poll();
if(node.val > res)
res = node.val;
if(node.left!=null) {
queue.add(node.left);
}
if(node.right!=null) {
queue.add(node.right);
}
}
list.add(res);
}
return list;
}
}
——leetcode 116、117.填充每个节点的下一个右侧节点指针(II)
题目:给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:
class Node {
public int val;
public Node left;
public Node right;
public Node next;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, Node _left, Node _right, Node _next) {
val = _val;
left = _left;
right = _right;
next = _next;
}
};
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL。
输入:root = [1,2,3,4,5,6,7] 输出:[1,#,2,3,#,4,5,6,7,#] 解释:给定二叉树如图 A 所示,你的函数应该填充它的每个 next 指针,以指向其下一个右侧节点,如图 B 所示。序列化的输出按层序遍历排列,同一层节点由 next 指针连接,‘#’ 标志着每一层的结束。
class Solution {
public Node connect(Node root) {
if(root ==null){
return root;
}
Queue<Node> queue = new LinkedList<>();
queue.add(root);
while(!queue.isEmpty()){
int n = queue.size();
for(int i=0;i<n;i++) {
Node node1 = queue.poll();
Node node2 = queue.peek();
if(i==n-1) node2 = null;
node1.next = node2;
if(node1.left!=null) {
queue.add(node1.left);
}
if(node1.right!=null) {
queue.add(node1.right);
}
}
}
return root;
}
}
优化方案:可以看成更链表(拓展)
public Node connect(Node root) {
if (root == null)
return root;
Node cur = root;
while (cur != null) {
Node dummy = new Node(0);
Node pre = dummy;
while (cur != null) {
if (cur.left != null) {
pre.next = cur.left;
pre = pre.next;
}
if (cur.right != null) {
pre.next = cur.right;
pre = pre.next;
}
cur = cur.next;
}
cur = dummy.next;
}
return root;
}
——leetcode 104、111.二叉树的最大深度,最小深度
题目:给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
class Solution {
public int maxDepth(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
int maxdepth = 0;
if(root == null) return maxdepth;
queue.add(root);
while(!queue.isEmpty()) {
int n = queue.size();
for(int i = 0;i < n;i++) {
TreeNode node = queue.poll();
if(node.left!=null) {
queue.add(node.left);
}
if(node.right!=null) {
queue.add(node.right);
}
}
maxdepth++;
}
return maxdepth;
}
}
class Solution {
public int maxDepth(TreeNode root) {
if(root == null) {
return 0;
} else {
int left = maxDepth(root.left);
int right = maxDepth(root.right);
return Math.max(left, right) + 1;
}
}
}
class Solution {
public int minDepth(TreeNode root) {
if(root == null) return 0;
int L = minDepth(root.left);
int R = minDepth(root.right);
if (root.left == null) return R+1;
if (root.right == null) return L+1;
return Math.min(L, R) + 1;
}
}
——leetcode 559.n叉树的最大深度
class Solution {
public int maxDepth(Node root) {
if(root == null) return 0;
int depth = 0;
for(Node node:root.children) {
depth = Math.max(depth,maxDepth(node));
}
return depth + 1;
}
}
class Solution {
public int maxDepth(Node root) {
if(root == null) return 0;
Queue<Node> queue = new LinkedList<>();
queue.add(root);
int res = 0;
while(!queue.isEmpty()) {
int n =queue.size();
for(int i=0;i<n;i++){
Node node = queue.poll();
for(Node cd:node.children) {
queue.add(cd);
}
}
res++;
}
return res;
}
}
——leetcode 226.翻转二叉树
题目:给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。(实质上为翻转每一个子节点)
class Solution {
public TreeNode invertTree(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
if(root == null) return root;
queue.add(root);
while(!queue.isEmpty()) {
TreeNode node =queue.poll();
TreeNode temp =node.left;
node.left = node.right;
node.right =temp;
if(node.left!=null){
queue.add(node.left);
}
if(node.right!=null){
queue.add(node.right);
}
}
return root;
}
}
class Solution {
public TreeNode invertTree(TreeNode root) {
if (root == null) {
return null;
}
invertTree(root.left);
invertTree(root.right);
swapChildren(root);
return root;
}
private void swapChildren(TreeNode root) {
TreeNode tmp = root.left;
root.left = root.right;
root.right = tmp;
}
}
1.3、对称二叉树
——leetcode 101. 对称二叉树
题目:给你一个二叉树的根节点 root , 检查它是否轴对称。
输入:root = [1,2,2,3,4,4,3]
输出:true
class Solution {
public boolean isSymmetric(TreeNode root) {
return Compare(root.left,root.right);
}
public boolean Compare(TreeNode left,TreeNode right) {
if (left == null && right != null) {
return false;
}
if (left != null && right == null) {
return false;
}
if (left == null && right == null) {
return true;
}
if (left.val != right.val) {
return false;
}
boolean compareOutside = Compare(left.left, right.right);
boolean compareInside = Compare(left.right, right.left);
return compareOutside && compareInside;
}
}
class Solution {
public boolean isSymmetric(TreeNode root) {
Deque<TreeNode> deque = new LinkedList<>();
deque.offer(root.left);
deque.offer(root.right);
while(!deque.isEmpty()) {
TreeNode nodeleft = deque.poll();
TreeNode noderight = deque.pollLast();
if(nodeleft == null && noderight == null) continue;
if(nodeleft==null||noderight==null||nodeleft.val!=noderight.val) {
return false;
}
deque.addFirst(nodeleft.left);
deque.add(noderight.right);
deque.addFirst(nodeleft.right);
deque.add(noderight.left);
}
return true;
}
}
class Solution {
public boolean isSymmetric(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root.left);
queue.add(root.right);
while(!queue.isEmpty()) {
TreeNode nodeleft = queue.poll();
TreeNode noderight = queue.poll();
if(nodeleft == noderight) continue;
if(nodeleft==null||noderight==null||nodeleft.val!=noderight.val) {
return false;
}
queue.add(nodeleft.left);
queue.add(noderight.right);
queue.add(nodeleft.right);
queue.add(noderight.left);
}
return true;
}
}
——leetcode 100. 相同的树
题目:给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。
如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
if(p==null&&q==null) return true;
if(p==null||q==null||p.val!=q.val) return false;
boolean same = isSameTree(p.left,q.left);
boolean samer = isSameTree(p.right,q.right);
return same && samer;
}
}
——leetcode 572. 另一棵树的子树
给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true ;否则,返回 false 。
二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子树。
class Solution {
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
if (root == null) {
return false;
}
return check(root, subRoot) || isSubtree(root.left, subRoot) || isSubtree(root.right, subRoot);
}
public boolean check(TreeNode s, TreeNode t) {
if (s == null && t == null) {
return true;
}
if (s == null || t == null || s.val != t.val) {
return false;
}
return check(s.left, t.left) && check(s.right, t.right);
}
}
1.4、对称二叉树
——leetcode 222. 完全二叉树的节点个数
题目:给你一棵 完全二叉树 的根节点 root ,求出该树的节点个数。
完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2^h个节点。
输入:root = [1,2,3,4,5,6]
输出:6
class Solution {
public int countNodes(TreeNode root) {
if(root == null) return 0;
int left = countNodes(root.left);
int right = countNodes(root.right);
return left+right+1;
}
}
class Solution {
public int countNodes(TreeNode root) {
if(root == null) return 0;
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
int res = 0;
while(!queue.isEmpty()){
int n = queue.size();
for(int i = 0;i < n; i++){
TreeNode node = queue.poll();
if(node.left!=null) queue.add(node.left);
if(node.right!=null) queue.add(node.right);
res++;
}
}
return res;
}
}
class Solution {
public int countNodes(TreeNode root) {
if(root == null) {
return 0;
}
int leftDepth = getDepth(root.left);
int rightDepth = getDepth(root.right);
if (leftDepth == rightDepth) {
return (1 << leftDepth) + countNodes(root.right);
} else {
return (1 << rightDepth) + countNodes(root.left);
}
}
private int getDepth(TreeNode root) {
int depth = 0;
while (root != null) {
root = root.left;
depth++;
}
return depth;
}
}
1.5、平衡二叉树
给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。
?
输入:root = [3,9,20,null,null,15,7]
输出:true
public int depth(TreeNode root) {
if (root == null) return 0;
return Math.max(depth(root.left), depth(root.right)) + 1;
}
class Solution {
public boolean isBalanced(TreeNode root) {
if (root == null) return true;
return Math.abs(depth(root.left) - depth(root.right)) <= 1 && isBalanced(root.left) && isBalanced(root.right);
}
private int depth(TreeNode root) {
if (root == null) return 0;
return Math.max(depth(root.left), depth(root.right)) + 1;
}
}
class Solution {
public boolean isBalanced(TreeNode root) {
return recur(root)!=-1;
}
public int recur (TreeNode root) {
if(root == null) return 0;
int left = recur(root.left);
if(left == -1) return -1;
int right = recur(root.right);
if(right == -1) return -1;
return Math.abs(left-right)<2? Math.max(left,right)+1:-1;
}
}
1.6、二叉树的所有路径
——leetcode 257. 二叉树的所有路径
题目:给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。
叶子节点 是指没有子节点的节点。
输入:root = [1,2,3,null,5]
输出:["1->2->5","1->3"]
方法一:深度优先搜索 思路与算法
最直观的方法是使用深度优先搜索。在深度优先搜索遍历二叉树时,我们需要考虑当前的节点以及它的孩子节点。
如果当前节点不是叶子节点,则在当前的路径末尾添加该节点,并继续递归遍历该节点的每一个孩子节点。 如果当前节点是叶子节点,则在当前路径末尾添加该节点后我们就得到了一条从根节点到叶子节点的路径,将该路径加入到答案即可。 如此,当遍历完整棵二叉树以后我们就得到了所有从根节点到叶子节点的路径。当然,深度优先搜索也可以使用非递归的方式实现,这里不再赘述。
class Solution {
public List<String> binaryTreePaths(TreeNode root) {
List<String> res = new LinkedList<>();
String st = "";
TreePath(root,st,res);
return res;
}
public void TreePath(TreeNode root,String st,List res){
if(root == null) return;
StringBuffer sb = new StringBuffer(st);
sb.append(Integer.toString(root.val));
if(root.left==null && root.right==null) {
res.add(sb.toString());
}
else{
sb.append("->");
TreePath(root.left,sb.toString(),res);
TreePath(root.right,sb.toString(),res);
}
}
}
方法二:广度优先搜索 思路与算法
我们也可以用广度优先搜索来实现。我们维护一个队列,存储节点以及根到该节点的路径。一开始这个队列里只有根节点。在每一步迭代中,我们取出队列中的首节点,如果它是叶子节点,则将它对应的路径加入到答案中。如果它不是叶子节点,则将它的所有孩子节点加入到队列的末尾。当队列为空时广度优先搜索结束,我们即能得到答案。
class Solution {
public List<String> binaryTreePaths(TreeNode root) {
List<String> paths = new ArrayList<String>();
if (root == null) {
return paths;
}
Queue<TreeNode> nodeQueue = new LinkedList<TreeNode>();
Queue<String> pathQueue = new LinkedList<String>();
nodeQueue.offer(root);
pathQueue.offer(Integer.toString(root.val));
while (!nodeQueue.isEmpty()) {
TreeNode node = nodeQueue.poll();
String path = pathQueue.poll();
if (node.left == null && node.right == null) {
paths.add(path);
} else {
if (node.left != null) {
nodeQueue.offer(node.left);
pathQueue.offer(new StringBuffer(path).append("->").append(node.left.val).toString());
}
if (node.right != null) {
nodeQueue.offer(node.right);
pathQueue.offer(new StringBuffer(path).append("->").append(node.right.val).toString());
}
}
}
return paths;
}
}
方法三:回溯算法 思路与算法
这道题目要求从根节点到叶子的路径,所以需要前序遍历,这样才方便让父节点指向孩子节点,找到对应的路径。在这道题目中将第一次涉及到回溯,因为我们要把路径记录下来,需要回溯来回退一一个路径在进入另一个路径。前序遍历以及回溯的过程如图:
class Solution {
public List<String> binaryTreePaths(TreeNode root) {
List<String> res = new ArrayList<>();
if (root == null) {
return res;
}
List<Integer> paths = new ArrayList<>();
traversal(root, paths, res);
return res;
}
private void traversal(TreeNode root, List<Integer> paths, List<String> res) {
paths.add(root.val);
if (root.left == null && root.right == null) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < paths.size() - 1; i++) {
sb.append(paths.get(i)).append("->");
}
sb.append(paths.get(paths.size() - 1));
res.add(sb.toString());
return;
}
if (root.left != null) {
traversal(root.left, paths, res);
paths.remove(paths.size() - 1);
}
if (root.right != null) {
traversal(root.right, paths, res);
paths.remove(paths.size() - 1);
}
}
}
1.7、左叶子之和
——leetcode 404.左叶子之和
题目:给定二叉树的根节点 root ,返回所有左叶子之和。
输入: root = [3,9,20,null,null,15,7]
输出: 24
解释: 在这个二叉树中,有两个左叶子,分别是 9 和 15,所以返回 24
class Solution {
public int sumOfLeftLeaves(TreeNode root) {
List<Integer> res = new LinkedList<>();
LeftLeaves(root,res);
int sum = 0;
for(int i:res) {
sum += i;
}
return sum;
}
public void LeftLeaves(TreeNode root,List res) {
if(root == null) return;
if(root.left!=null&&root.left.left==null&&root.left.right==null) {
res.add(root.left.val);
}
if(root.left!=null) LeftLeaves(root.left,res);
if(root.right!=null) LeftLeaves(root.right,res);
}
}
1.8、找树左下角的值
——leetcode 513.找树左下角的值
题目:给定一个二叉树的 根节点 root ,请找出该二叉树的 最底层 最左边 节点的值。
假设二叉树中至少有一个节点。
输入: [1,2,3,4,null,5,6,null,null,7]
输出: 7
class Solution {
int Deep = -1;
int num = 0;
public int findBottomLeftValue(TreeNode root) {
findValue(root,0);
return num;
}
public void findValue(TreeNode root,int depth) {
if(root == null) return;
depth++;
if(root.left==null&&root.right==null){
if(depth > Deep) {
Deep=depth;
num = root.val;
}
}
if(root.left!=null) {
findValue(root.left,depth);
}
if(root.right!=null) {
findValue(root.right,depth);
}
}
}
class Solution {
public int findBottomLeftValue(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
int num = root.val;
while(!queue.isEmpty()){
int n = queue.size();
for(int i = 0;i<n;i++){
TreeNode node = queue.poll();
if(node.left!=null){
queue.add(node.left);
}
if(node.right!=null){
queue.add(node.right);
}
if(i == 0) {
num = node.val;
}
}
}
return num;
}
}
1.9、路径总和
——leetcode 112路径总和
题目:给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false 。叶子节点 是指没有子节点的节点。
输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
输出:true
解释:等于目标和的根节点到叶节点路径如上图所示。
class Solution {
public boolean hasPathSum(TreeNode root, int targetSum) {
return PathSum(root,targetSum,0);
}
public boolean PathSum(TreeNode root, int targetSum, int sum) {
if(root == null) return false;
sum += root.val;
boolean left = PathSum(root.left,targetSum,sum);
boolean right = PathSum(root.right,targetSum,sum);
if(root.left==null&&root.right==null) {
if(targetSum == sum) return true;
}
return left||right;
}
}
public boolean hasPathSum(TreeNode root, int targetSum) {
if (root==null) return false;
LinkedList<TreeNode> queue = new LinkedList<>();
queue.offer(root);
LinkedList<Integer> sumQueue = new LinkedList<>();
sumQueue.offer(0);
while (queue.size()>0){
int size = queue.size();
for (int i = 0; i < size; i++) {
TreeNode poll = queue.poll();
Integer sum = sumQueue.poll();
if (poll.left==null && poll.right==null && (poll.val+sum) == targetSum){
return true;
}
if (poll.left!=null){
queue.offer(poll.left);
sumQueue.offer(sum+poll.val);
}
if (poll.right!=null){
queue.offer(poll.right);
sumQueue.offer(sum+poll.val);
}
}
}
return false;
}
——leetcode 113路径总和ii
题目:给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
叶子节点 是指没有子节点的节点。
注意:在list.add时,一定要新建对象,因为增加的是对象的地址!
res.add(new LinkedList(list));
输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出:[[5,4,11,2],[5,8,4,5]]
class Solution {
List<List<Integer>> res = new LinkedList<>();
List<Integer> list = new LinkedList<>();
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
path(root, targetSum);
return res;
}
public void path(TreeNode root, int targetSum) {
if(root == null) return;
list.add(root.val);
if(root.left == null && root.right == null && targetSum == root.val) {
res.add(new LinkedList(list));
}
pathSum(root.left,targetSum-root.val);
pathSum(root.right,targetSum-root.val);
list.remove(list.size()-1);
}
}
1.10、从两种排序遍历序列构造二叉树
——leetcode 106从中序与后序遍历序列构造二叉树
题目:给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返返回这颗 二叉树 。
树的还原过程描述
根据中序遍历和后续遍历的特性我们进行树的还原过程分析
- 首先在后序遍历序列中找到根节点(最后一个元素)
- 根据根节点在中序遍历序列中找到根节点的位置
- 根据根节点的位置将中序遍历序列分为左子树和右子树
- 根据根节点的位置确定左子树和右子树在中序数组和后续数组中的左右边界位置
- 递归构造左子树和右子树
- 返回根节点结束
树的还原过程变量定义
需要定义几个变量帮助我们进行树的还原
- HashMap memo 需要一个哈希表来保存中序遍历序列中,元素和索引的位置关系.因为从后序序列中拿到根节点后,要在中序序列中查找对应的位置,从而将数组分为左子树和右子树
- int ri 根节点在中序遍历数组中的索引位置
- 中序遍历数组的两个位置标记
[is, ie] ,is 是起始位置,ie 是结束位置 - 后序遍历数组的两个位置标记
[ps, pe] ps 是起始位置,pe 是结束位置
位置关系的计算
在找到根节点位置以后,我们要确定下一轮中,左子树和右子树在中序数组和后续数组中的左右边界的位置。
-
左子树-中序数组 is = is , ie = ri - 1 -
子树-后序数组 ps = ps , pe = ps + ri - is - 1 (pe计算过程解释,后续数组的起始位置加上左子树长度-1 就是后后序数组结束位置了,左子树的长度 = 根节点索引-左子树) -
右子树-中序数组 is = ri + 1, ie = ie -
右子树-后序数组 ps = ps + ri - is, pe - 1
class Solution {
HashMap<Integer,Integer> map = new HashMap<>();
int[] post;
public TreeNode buildTree(int[] inorder, int[] postorder) {
for(int i = 0; i < inorder.length; i++) {
map.put(inorder[i],i);
}
post = postorder;
TreeNode node = Tree(0,inorder.length-1,0,postorder.length-1);
return node;
}
public TreeNode Tree(int is, int ie, int ps, int pe) {
if(is > ie || ps > pe) return null;
int root = post[pe];
int ri = map.get(root);
TreeNode node = new TreeNode(root);
node.left = Tree(is,ri-1,ps,ps+ri-is-1);
node.right = Tree(ri+1,ie,ps+ri-is,pe-1);
return node;
}
}
——leetcode 105从前序与中序遍历序列构造二叉树
题目:给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。
输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]
class Solution {
HashMap<Integer,Integer> map = new HashMap<>();
int[] pre;
public TreeNode buildTree(int[] preorder, int[] inorder) {
for(int i = 0; i < inorder.length; i++){
map.put(inorder[i],i);
}
pre = preorder;
TreeNode node = Tree(0,preorder.length-1,0,inorder.length-1);
return node;
}
public TreeNode Tree(int is,int ie,int ps,int pe){
if(is > ie || ps > pe) return null;
int root = pre[is];
int ri = map.get(root);
TreeNode node = new TreeNode(root);
node.left = Tree(is+1,ri-ps+is,ps,ri-1);
node.right = Tree(ri-ps+is+1,ie,ri+1,pe);
return node;
}
}
1.11、最大二叉树
——leetcode 654. 最大二叉树
题目:给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建:
- 创建一个根节点,其值为
nums 中的最大值。 - 递归地在最大值 左边 的 子数组前缀上 构建左子树。
- 递归地在最大值 右边 的 子数组后缀上 构建右子树。
返回 nums 构建的 *最大二叉树* 。
输入:nums = [3,2,1,6,0,5]
输出:[6,3,5,null,2,0,null,null,1]
解释:递归调用如下所示:
- [3,2,1,6,0,5] 中的最大值是 6 ,左边部分是 [3,2,1] ,右边部分是 [0,5] 。
- [3,2,1] 中的最大值是 3 ,左边部分是 [] ,右边部分是 [2,1] 。
- 空数组,无子节点。
- [2,1] 中的最大值是 2 ,左边部分是 [] ,右边部分是 [1] 。
- 空数组,无子节点。
- 只有一个元素,所以子节点是一个值为 1 的节点。
- [0,5] 中的最大值是 5 ,左边部分是 [0] ,右边部分是 [] 。
- 只有一个元素,所以子节点是一个值为 0 的节点。
- 空数组,无子节点。
class Solution {
public TreeNode constructMaximumBinaryTree(int[] nums) {
return constructMaximumBinaryTree1(nums, 0, nums.length-1);
}
public TreeNode constructMaximumBinaryTree1(int[] nums, int leftIndex, int rightIndex) {
if (rightIndex - leftIndex < 0) {
return null;
}
if (rightIndex - leftIndex == 0) {
return new TreeNode(nums[leftIndex]);
}
int maxIndex = leftIndex;
int maxVal = nums[maxIndex];
for (int i = leftIndex ; i <=rightIndex; i++) {
if (nums[i] > maxVal){
maxVal = nums[i];
maxIndex = i;
}
}
TreeNode root = new TreeNode(maxVal);
root.left = constructMaximumBinaryTree1(nums, leftIndex, maxIndex-1);
root.right = constructMaximumBinaryTree1(nums, maxIndex + 1, rightIndex);
return root;
}
}
——leetcode 617. 合并二叉树
题目:给你两棵二叉树: root1 和 root2 。
? 想象一下,当你将其中一棵覆盖到另一棵之上时,两棵树上的一些节点将会重叠(而另一些不会)。你需要将这两棵树合并成一棵新二叉树。合并的规则是:如果两个节点重叠,那么将这两个节点的值相加作为合并后节点的新值;否则,不为 null 的节点将直接作为新二叉树的节点。返回合并后的二叉树。
? 注意: 合并过程必须从两个树的根节点开始。
class Solution {
public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
if(root1 == null) return root2;
if(root2 == null) return root1;
TreeNode root = new TreeNode(root1.val + root2.val);
root.left = mergeTrees(root1.left,root2.left);
root.right = mergeTrees(root1.right,root2.right);
return root;
}
}
class Solution {
public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
if (root1 == null) return root2;
if (root2 ==null) return root1;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root1);
queue.offer(root2);
while (!queue.isEmpty()) {
TreeNode node1 = queue.poll();
TreeNode node2 = queue.poll();
node1.val = node1.val + node2.val;
if (node1.left != null && node2.left != null) {
queue.offer(node1.left);
queue.offer(node2.left);
}
if (node1.right != null && node2.right != null) {
queue.offer(node1.right);
queue.offer(node2.right);
}
if (node1.left == null && node2.left != null) {
node1.left = node2.left;
}
if (node1.right == null && node2.right != null) {
node1.right = node2.right;
}
}
return root1;
}
}
1.12、二叉搜索树
——leetcode 700. 二叉搜索树中的搜索
题目:给定二叉搜索树(BST)的根节点 root 和一个整数值 val 。
你需要在 BST 中找到节点值等于 val 的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 null 。
输入:root = [4,2,7,1,3], val = 2
输出:[2,1,3]
class Solution {
public TreeNode searchBST(TreeNode root, int val) {
if (root == null || root.val == val) {
return root;
}
if (val < root.val) return searchBST(root.left,val);
else return searchBST(root.right,val);
}
}
class Solution {
public TreeNode searchBST(TreeNode root, int val) {
while (root != null)
if (val < root.val) root = root.left;
else if (val > root.val) root = root.right;
else return root;
return null;
}
}
——leetcode 98. 验证二叉搜索树
题目:给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
- 节点的左子树只包含 小于 当前节点的数。
- 节点的右子树只包含 大于 当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
输入:root = [5,1,4,null,null,3,6]
输出:false
解释:根节点的值是 5 ,但是右子节点的值是 4 。
class Solution {
public boolean isValidBST(TreeNode root) {
List<Integer> list = new ArrayList<>();
inOrder(root,list);
for(int i = 1;i < list.size();i++) {
if(list.get(i)<=list.get(i-1)){
return false;
}
}
return true;
}
public void inOrder (TreeNode root,List list) {
if(root == null) return;
inOrder(root.left,list);
list.add(root.val);
inOrder(root.right,list);
}
}
class Solution {
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);
}
}
——leetcode 530. 二叉搜索树的最小绝对差
题目:给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值 。
差值是一个正数,其数值等于两值之差的绝对值。
输入:root = [4,2,6,1,3]
输出:1
class Solution {
TreeNode pre;
int result = Integer.MAX_VALUE;
public int getMinimumDifference(TreeNode root) {
if(root==null) return 0;
traversal(root);
return result;
}
public void traversal(TreeNode root){
if(root==null) return;
traversal(root.left);
if(pre!=null){
result = Math.min(result,Math.abs(pre.val-root.val));
}
pre = root;
traversal(root.right);
}
}
——leetcode 501.二叉搜索树中的众数
题目:给你一个含重复值的二叉搜索树(BST)的根节点 root ,找出并返回 BST 中的所有众数(即,出现频率最高的元素)。
如果树中有不止一个众数,可以按 任意顺序 返回。
假定 BST 满足如下定义:
- 结点左子树中所含节点的值 小于等于 当前节点的值
- 结点右子树中所含节点的值 大于等于 当前节点的值
- 左子树和右子树都是二叉搜索树
输入:root = [1,null,2,2]
输出:[2]
class Solution {
List<Integer> list;
int count;
int maxcount;
TreeNode pre;
public int[] findMode(TreeNode root) {
list = new LinkedList<>();
inorder(root);
int[] res = new int[list.size()];
for(int i = 0;i < list.size();i++) {
res[i] = list.get(i);
}
return res;
}
public void inorder(TreeNode root) {
if(root == null) return;
inorder(root.left);
if(pre==null || pre.val != root.val){
count = 1;
}
else count++;
if(count > maxcount) {
maxcount = count;
list.clear();
list.add(root.val);
}
else if(count == maxcount) {
list.add(root.val);
}
pre = root;
inorder(root.right);
}
}
——leetcode 236. 二叉树的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出:3
解释:节点 5 和节点 1 的最近公共祖先是节点 3 。
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出:5
解释:节点 5 和节点 4 的最近公共祖先是节点 5 。因为根据定义最近公共祖先节点可以为节点本身。
这道题目刷过的同学未必真正了解这里面回溯的过程,以及结果是如何一层一层传上去的。
那么我给大家归纳如下三点:
- 求最小公共祖先,需要从底向上遍历,那么二叉树,只能通过后序遍历(即:回溯)实现从低向上的遍历方式。
- 在回溯的过程中,必然要遍历整棵二叉树,即使已经找到结果了,依然要把其他节点遍历完,因为要使用递归函数的返回值(也就是代码中的left和right)做逻辑判断。
- 要理解如果返回值left为空,right不为空为什么要返回right,为什么可以用返回right传给上一层结果。
可以说这里每一步,都是有难度的,都需要对二叉树,递归和回溯有一定的理解。
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);
if(left == null && right == null) {
return null;
}else if(left == null && right != null) {
return right;
}else if(left != null && right == null) {
return left;
}else {
return root;
}
}
}
——leetcode 235. 二叉树的最近公共祖先
利用回溯从底向上搜索,遇到一个节点的左子树里有p,右子树里有q,那么当前节点就是最近公共祖先。
那么本题是二叉搜索树,二叉搜索树是有序的,那得好好利用一下这个特点。
在有序树里,如果判断一个节点的左子树里有p,右子树里有q呢?
其实只要从上到下遍历的时候,cur节点是数值在[p, q]区间中则说明该节点cur就是最近公共祖先了。
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) {
while (true) {
if (root.val > p.val && root.val > q.val) {
root = root.left;
} else if (root.val < p.val && root.val < q.val) {
root = root.right;
} else {
break;
}
}
return root;
}
}
——leetcode 701. 二叉搜索树中的插入操作
给定二叉搜索树(BST)的根节点 root 和要插入树中的值 value ,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。
注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果 。
输入:root = [4,2,7,1,3], val = 5
输出:[4,2,7,1,3,5]
解释:另一个满足题目要求可以通过的树是:
解析:第二种情况有点误导别人,其实加入的元素一定在树的底层!
class Solution {
public TreeNode insertIntoBST(TreeNode root, int val) {
if (root == null)
return new TreeNode(val);
if (root.val < val){
root.right = insertIntoBST(root.right, val);
}else if (root.val > val){
root.left = insertIntoBST(root.left, val);
}
return root;
}
}
class Solution {
public TreeNode insertIntoBST(TreeNode root, int val) {
if (root == null) return new TreeNode(val);
TreeNode newRoot = root;
TreeNode pre = root;
while(true) {
if (root == null) break;
pre = root;
if (root.val < val){
root = root.right;
}
else if (root.val > val){
root = root.left;
}
}
if (pre.val > val) pre.left = new TreeNode(val);
if (pre.val < val) pre.right = new TreeNode(val);
return newRoot;
}
——leetcode 669. 修剪二叉搜索树
题目:给你二叉搜索树的根节点 root ,同时给定最小边界low 和最大边界 high 。通过修剪二叉搜索树,使得所有节点的值在[low, high] 中。修剪树 不应该 改变保留在树中的元素的相对结构 (即,如果没有被移除,原有的父代子代关系都应当保留)。 可以证明,存在 唯一的答案 。
所以结果应当返回修剪好的二叉搜索树的新的根节点。注意,根节点可能会根据给定的边界发生改变。
输入:root = [1,0,2], low = 1, high = 2
输出:[1,null,2]
class Solution {
public TreeNode trimBST(TreeNode root, int low, int high) {
if(root == null) return root;
if(root.val < low) return trimBST(root.right,low,high);
if(root.val > high) return trimBST(root.left,low,high);
if(root.val >= low && root.val <= high) {
root.left = trimBST(root.left,low,high);
root.right = trimBST(root.right,low,high);
}
return root;
}
}
——leetcode 108. 将有序数组转换为二叉搜索树
题目:给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。
高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。
输入:nums = [-10,-3,0,5,9]
输出:[0,-3,9,-10,null,5]
解释:[0,-10,5,null,-3,null,9] 也将被视为正确答案
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
return traversal(nums,0,nums.length-1);
}
public TreeNode traversal(int[] nums,int left,int right) {
if(left > right) return null;
int mid = left + (right - left)/2;
TreeNode root = new TreeNode(nums[mid]);
root.left = traversal(nums,left,mid-1);
root.right = traversal(nums,mid+1,right);
return root;
}
}
——leetcode 538. 把二叉搜索树转换为累加树
题目:给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。
输入:[4,1,6,0,2,5,7,null,null,null,3,null,null,null,8]
输出:[30,36,21,36,35,26,15,null,null,null,33,null,null,null,8]
class Solution {
int sum = 0;
public TreeNode convertBST(TreeNode root) {
convertBST1(root);
return root;
}
public void convertBST1(TreeNode root) {
if(root == null) return;
convertBST1(root.right);
sum += root.val;
root.val = sum;
convertBST1(root.left);
}
}
|