给你二叉树的根节点 root和一个整数目标和targetSum,找出所有从根节点到叶子节点路径总和等于给定目标和的路径。
思路 : 深度优先搜索,进行递归遍历即可解决(回溯法)
分先序遍历+路径记录两部分
先序遍历:根左右顺序遍历二叉树所有节点
路径记录:在先序遍历中,记录从根节点到当前节点的路径。当路径为 ① 根节点到叶节点形成的路径且② 各节点值的和等于目标值target时,将此路径加入结果列表
递归函数:
1)递推参数:当前节点 root,当前目标值target
2)终止条件:若节点root为空,则直接返回
3)递推工作:
a.路径更新: 将当前节点值 root.val 加入路径 path ;
b.目标值更新:target=target-root.val(即目标值target减至0);
c.路径记录:当 ① root 为叶子节点 且 ② 路径和等于目标值 ,则将此路径path加入res
d.先序遍历:递归左 / 右子节点。
e.路径恢复:向上回溯前,需要将当前节点从路径 path 中删除,即执行 path.pop()
注意:将路径假如结果集中时,不能直接加入。因为若直接执行
res.append(path),则是将path对象加入了res;后续path改变时,res中的path对象也会随之改变。正确写法:
res.append(list(path)),相当于复制了一个path并加入到res
- public class Num34_二叉树中和为某一值的路径 {
- private class TreeNode {
- int val;
- TreeNode left;
- TreeNode right;
- TreeNode() {}
- TreeNode(int val) { this.val = val; }
- TreeNode(int val, TreeNode left, TreeNode right) {
- this.val = val;
- this.left = left;
- this.right = right;
- }
- }
- LinkedList
> res=new LinkedList<>();//记录最终结果:所有符合条件的路径
- LinkedList
path=new LinkedList<>();//遍历到的符合条件的某个路径 - public List
> pathSum(TreeNode root, int target) {
- recur(root,target);
- return res;
- }
-
- private void recur(TreeNode root, int target) {
- //1.递归终止条件:root==null
- if(root==null){
- return;
- }
- //2.将当前根节点的值加入路径,同时target减去根节点的值。
- // 当根节点值归零同时左右子树为空,则当前节点就是叶子节点且满足所有值和等于target(因为target减完所有节点值自己值就归零,以此判断相等)
- //此时该路径就是结点和为target的路径之一,将该路径加入结果集res中
- path.add(root.val);
- target=target-root.val;
- if(target==0&&root.left==null&&root.right==null){
- res.add(new LinkedList<>(path));
- }
- //3.遍历左右子树
- recur(root.left,target);
- recur(root.right,target);
- //4.回溯计算是否有其他对应路径时应先将当前节点从路径path中删除
- path.removeLast();
- }
- }
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。
算法思路流程:
1.递归法中序遍历-转为双向链表:终止条件:当节点cur为空,代表越过叶节点,直接返回;
递归左子树,即 dfs(cur.left) ;
构建链表:
1)当 pre 为空时:代表正在访问链表头节点,记为 head ;
2)当 pre 不为空时:修改双向节点引用,即pre.right=cur,cur.left=pre;
保存cur: 更新 pre = cur ,即节点 cur 是后继节点的 pre ;
递归右子树,即 dfs(cur.right) ;
2.treeToDoublyList(root):特例处理:若节点root为空,则直接返回;
初始化:空节点pre;
转化为双向链表:调用 dfs(root) ;
构建循环链表: 中序遍历完成后,head 指向头节点, pre 指向尾节点,因此修改 head 和 pre 的双向节点引用即可;
返回值: 返回链表的头节点 head 即可;
- Node pre;//前驱结点
- Node head;//头节点
- public Node treeToDoublyList(Node root) {
- if(root==null){
- return null;
- }
- //此时节点不为空,构建有序双向链表
- dfs(root);
- //此时得到了链表是有序双向链表,头节点是head,尾结点是prev(cur走到空时pre刚好走到尾部)
- //头节点.pre是尾结点,尾结点.next是头节点即得到循环链表
- head.left=pre;
- pre.right=head;
- return head;
- }
- //二叉树转为有序双向链表
- //有序:通过中序遍历实现:中序遍历结果是一个排序好的顺序值
- //这个方法的用途就是把二叉树转为有序双向链表
- private void dfs(Node cur) {
- //1.当前结点cur为空,已越过叶子节点,直接返回
- if(cur==null){
- return;
- }
- //2.遍历左子树
- dfs(cur.left);
- //3.处理节点
- //判断前驱pre是否为null,为null说明cur是头节点
- if(pre==null){
- head=cur;
- }else{
- pre.right=cur;
- }
- cur.left=pre;
- //更新pre的值为cur:即节点cur是后继节点的pre
- pre=cur;
- /**
- * if(prev!=null){
- * //prev的下一位是待处理的根节点,但不是头节点
- * //连接
- * prev.right=root;
- * root.left=prev;
- * //prev往后走一位
- * prev=root;
- * }else{
- * //prev的下一位即root就是头节点
- * head=root;
- * prev=head;
- * }
- */
- //4.遍历右子树
- dfs(cur.right);
- }
另一种写法:
- class Solution {
- Node tail;
- public Node treeToDoublyList(Node root) {
- if(root==null){
- return null;
- }
- //treeToDoublyListHelper():求双向有序列表
- Node head=treeToDoublyListHelper(root);
- //连接首尾
- head.left=tail;
- tail.right=head;
- return head;
- }
- public Node treeToDoublyListHelper(Node root) {
- if(root==null){
- return null;
- }
- //对二叉树进行中序遍历:保证顺序。
- //递归函数进行的操作是二叉树变为双向链表
- Node leftHead=treeToDoublyListHelper(root.left);
- Node leftTail=leftHead;
- //找左子树尾节点
- while(leftTail!=null&&leftTail.right!=null){
- leftTail=leftTail.right;
- }
- //此时leftTail是左子树尾节点
- if(leftTail!=null){
- leftTail.right=root;
- root.left=leftTail;
- }
- //左子树连接完毕
- //处理右边
- Node rightHead=treeToDoublyListHelper(root.right);
- if(rightHead!=null){
- rightHead.left=root;
- root.right=rightHead;
- }
- tail=rightHead;
- while(tail!=null&&tail.right!=null){
- tail=tail.right;//走完tail是尾节点
- }
- if(tail==null){
- tail=root;
- }
- //返回大链表的根节点(头节点):两种情况
- //左树为空返回root,不为空返回左树头节点
- return leftHead==null?root:leftHead;
- }
- }
给定一棵二叉搜索树,请找出其中第 k 大的节点的值。
分析:
1.二叉搜索树的中序遍历是一个递增序列,那么,中序遍历的倒叙就是递减序列。因此,求 “二叉搜索树第 kk 大的节点” 可转化为求 “此树的中序遍历倒序的第k个节点”。中序遍历:左根右,倒序:右左根。
2.为求第k个节点,需要实现以下三项工作:
1)递归遍历时计数,统计当前节点的序号;
2)递归到第k个节点时,应记录结果res;
3)记录结果后,后续的遍历即失去意义,应提前终止(即返回)。3.递归解析:
终止条件:当节点root为空(越过叶子节点),则直接返回;
递归右子树:即dfs(root.right) ;
三项工作:
a. 提前返回:若k=0,代表已找到目标节点,无需继续遍历,因此直接返回;
b.统计序号:执行k=k-1(即从k减至0);
c.记录结果:若k=0,代表当前节点为第 kk 大的节点,因此记录 res = root.valres=root.val ;
递归左子树:即dfs(root.left) ;
- public class Num54_二叉树第k大结点 {
- private class TreeNode {
- int val;
- TreeNode left;
- TreeNode right;
- TreeNode(int x) { val = x; }
- }
- int res;//结果
- int k;//第k大结点
- public int kthLargest(TreeNode root, int k) {
- this.k=k;
- //求二叉树第k大结点
- dfs(root);
- return res;
- }
- //中序遍历的倒叙:中序遍历得到一个升序值,她的倒叙就是一个降序,第k大就变为求第k个结点
- //中序:左根右;倒序后:右根左
- private void dfs(TreeNode root) {
- if(root==null){
- return;
- }
- dfs(root.right);
- //处理结点
- if(k==0){
- return;
- }
- if(--k==0){//k不断减减到0时,得到的那个结点就是第k大结点,拿到节点值直接返回即可
- res=root.val;
- }
- dfs(root.left);
- }
- }
- /**
- * 输入一棵二叉树的根节点,求该树的深度。
- * 从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。
- **/
-
- /**
- * 思路:二叉树的深度左右子树长度较长的那个的长度+根节点长度
- */
- public class Num55_I二叉树的深度 {
- public class TreeNode {
- int val;
- TreeNode left;
- TreeNode right;
- TreeNode(int x) { val = x; }
- }
- public int maxDepth(TreeNode root) {
- if(root==null){
- return 0;
- }
- //根节点存在,求左右子树深度
- return Math.max(maxDepth(root.left),maxDepth(root.right))+1;//加1是加上当前节点的长度
- }
- }
输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。
Math.abs(x-y):计算两个数字之间的距离值
- /**
- * 如何判断一个树root是否为平衡树:
- * 以root为根节点的二叉树左右子树的高度差<=1并且左右子树分别也都是平衡树
- */
- public class Num55_II平衡二叉树 {
- public class TreeNode {
- int val;
- TreeNode left;
- TreeNode right;
- TreeNode(int x) { val = x; }
- }
- public boolean isBalanced(TreeNode root) {
- if(root==null){
- return true;
- }
- //求当前节点左右子树高度差
- //当高度差<=1并且左右子树都是平衡二叉树即整个树是平衡二叉树
- return Math.abs(height(root.left)-height(root.right))<=1&&isBalanced(root.left)&&isBalanced(root.right);
- }
- //计算树的高度
- private int height(TreeNode node) {
- if(node==null){
- return 0;
- }
- return 1+Math.max(height(node.left),height(node.right));
- }
- }
在判断平衡树的过程中,若干个节点可能会被重复计算多次,造成了递归的重复执行,越靠下的节点,重复几率越高,可以使用记忆化搜索优化这种情况。
记忆化搜索:在递归过程中,若某个节点的高度已经被计算过了,我们就使用Map存储该节点以及其高度值,下次再用到这个节点的时候,我们直接从Map中取高度即可,避免多次重复的递归过程
- private Map
nodeMap=new HashMap<>();//存储结点及其对应高度 - public boolean isBalanced(TreeNode root) {
- if(root==null){
- return true;
- }
- int left=0;
- int right=0;
- if(nodeMap.containsKey(root.left)){
- //此时左树高度已经被计算过了,直接从Map中取出节点值对应高度
- left=nodeMap.get(root.left);
- }else {
- //否则,计算左子树高度,并将高度放入Map集合中
- left=height(root.left);
- nodeMap.put(root.left,left);
- }
- if(nodeMap.containsKey(root.right)){
- right=nodeMap.get(root.right);
- }else {
- right=height(root.right);
- nodeMap.put(root.right,right);
- }
- return Math.abs(left-right)<=1&&isBalanced(root.left)&&isBalanced(root.right);
- }
-
- private int height(TreeNode node) {
- if(node==null){
- return 0;
- }
- return 1+Math.max(height(node.left),height(node.right));
- }
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。最近公共祖先的定义为:“对于有根树T的两个结点p、q,最近公共祖先表示为一个结点x,满足x是p、q的祖先且x的深度尽可能大(一个节点也可以是它自己的祖先)。”
- /**
- * 用整型变量接收是否找到节点p,q
- * 我们在找p或q的祖先时顺便找最近公共祖先
- */
- public class Num68_I二叉树的最近公共祖先 {
- private class TreeNode {
- int val;
- TreeNode left;
- TreeNode right;
- TreeNode(int x) { val = x; }
- }
- //定义一个全局变量用于返回最近公共祖先
- private TreeNode resNode;
- public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
- lowestCommonAncestorHelper(root,p,q);
- return resNode;
- }
-
- /**
- * 以当前root为根节点的二叉树是否能找到p或q
- * 该方法不是找公共祖先的方法,只是判断当前树是否是p或q的祖先,找到一个就说明至少是一个结点的祖先
- * 只是找是否是p或q的祖先的过程中判断了是否是公共祖先
- */
- private Boolean lowestCommonAncestorHelper(TreeNode root, TreeNode p, TreeNode q) {
- if(root==null){
- return false;
- }
- //判断左右子树是否有包含p或q,根节点是否是p或q
- int left=lowestCommonAncestorHelper(root.left,p,q)?1:0;
- int right=lowestCommonAncestorHelper(root.right,p,q)?1:0;
- int mid=(root==p||root==q)?1:0;
- if(left+right+mid==2){
- resNode=root;//相加和为2,根节点就是最近公共祖先
- }
- //否则,有没有祖先就是看就是看上面有没有大于零的数
- return left+right+mid>0;
- }
- }