一、在二叉树中找到累加和为指定值的最长路径长度

  给定一棵二叉树和一个32位整数sum,求累加和为sum的最长路径长度。路径是指从某个节点往下,每次最多选择一个孩子节点或者不选所形成的节点链

              -3
/ \
3 -9
/ \ / \
1 0 2 1
/\ 如果sum=6,那么累加和为6的最长路径为:-3,3,0,6,所以返回4
1 6 如果sum=-9,那么累加和为-9的最长路径为:-9,所以返回1

  第一步:生成变量maxLen,记录累加和等于sum的最长路径长度

  第二步:生成哈希表sumMap,负责记录从根结点root开始的一条路径上的累加和出现情况。sumMap的key值代表某个累加和,value值代表这个累加和在路径中最早出现的层数。如果在遍历到cur节点的时候,已经知道了从root到cur节点这条路径上的累加和出现情况,那么求以cur节点结尾的累加和为指定值的最长路径长度就非常容易。那么问题是,如何去更新sumMap,才能够做到在遍历到任何一个节点的时候都能有从root到这个节点的路径上的累加和出现情况?

  第三步:首先在sumMap中加入一个记录(0,0),表示累加和0不用包括任何节点就可以得到。然后先序遍历节点,遍历到的当前节点记为cur,从root到cur父节点的累加和记为preSum,cur所在的层数记为level,将cur.val+preSum值记为curSum,就是从root到cur的累加和。如果sumMap中已经包含了curSum的记录,说明curSum在上层已经出现过,那么就不更新sumMap;如果sumMap不包含curSum的记录,说明curSum是第一次出现,就把(curSum,level)这个记录放入sumMap。在以cur为头节点的子树处理完,在返回cur父节点之前,还需要在sumMap中查询curSum这个累加和(key)出现的层数(value),如果value等于level,说明curSum这个累加和的记录是在遍历到cur时加上去的,那么就把这条记录删除,如果value不等于level,则不做任何调整。

  分析执行过程:

0.{0=0},curSum=0,
1.-3,{0=0, -3=1},curSum=-3,
2.-3 → 3,{0=0, -3=1},curSum=0,
3.-3 → 3 → 1,{0=0, 1=3, -3=1},curSum=1,
4.-3 → 3,{0=0, -3=1},curSum=0,
5.-3 → 3 → 0,{0=0, -3=1},curSum=0,
6.-3 → 3 → 0 → 1,{0=0, 1=4, -3=1},curSum=1,
7.-3 → 3 → 0, {0=0, -3=1},curSum=0,
8.-3 → 3 → 0 → 6, {0=0, -3=1, 6=4},curSum=6,
9.-3 → 3 → 0,{0=0, -3=1},curSum=0,
10.-3 → 3,{0=0, -3=1},curSum=0,
11.-3 ,{0=0, -3=1},curSum=0,
12.-3 → -9,{0=0, -3=1, -12=2},curSum=-12,
13.-3 → -9 → 2,{0=0, -3=1, -10=3, -12=2},curSum=-10,
14.-3 → -9 ,{0=0, -3=1, -12=2},curSum=-12,
15.-3 → -9 → 1,{0=0, -3=1, -11=3, -12=2},curSum=-11,
16.-3 → -9 ,{0=0, -3=1, -12=2},curSum=-12,
17.-3 ,{0=0, -3=1},curSum=-3,
18.{0=0,curSum=0,

  情况1:sum=6,根据 if ( sumMap.containsKey(curSum - sum)) { maxLen = Math.max(level - sumMap.get(curSum - sum), maxLen); }在上面的第8步,curSum-sum=0,maxLen=0,level-sumMap.get(curSum - sum)=4-0=0,因此maxLen=4。

  情况2:sum=-8,在上面的第15步,curSum=-11,而curSum-sum=-3,level-sumMap.get(curSum - sum)=3-1=2,因此maxLen=2,这里的意思就是,从根节点出发累加和为-11的时候需要走3level,而根据sumMap中的-3=1可以知道,从根节点出发累加和为-3的时候只需要走1level,因此要想实现累加和为-8只需要从1level走到3level即可。

  代码实现:

    public int getMaxLength(Node root, int sum) {
// key表示某个累加和,value表示这个累加和在路径中最早出现的层数
HashMap<Integer, Integer> sumMap = new HashMap<>();
// 表示累加和0不用经过任何节点就可以得到
sumMap.put(0, 0);
return preOrder(root, sum, 0, 1, 0, sumMap);
} /**
* @param root 遍历到当前节点,记为cur
* @param sum 要求的累加和
* @param preSum 从整棵树的根节点root到cur的父节点累加和
* @param level cur所在的层数
* @param sumMap 记录累加和与该累加和在路径中最早出现的层数
* @return maxLen 累加和等于sum的最长路径长度
*/
private int preOrder(Node root, int sum, int preSum, int level, int maxLen, HashMap<Integer, Integer> sumMap) {
if (root == null) return maxLen;
// 从root到cur的累加和等于从root到cur的父节点的累加和加上cur的val
int curSum = preSum + root.val;
// 如果sumMap不包含这个累加和,说明是第一次出现,就加入到sumMap中
if (!sumMap.containsKey(curSum)) sumMap.put(curSum, level);
// 如果curSum - sum已经包含了,就说明之前已经有了从根节点到达curSum - sum需要的level数,因此,
// 用从根节点到当前节点的level数减去从根节点到curSum - sum需要的level数就是从curSum - sum节点走到当前curSum节点的level数
// 也就是,curSum - (curSum - sum) = sum
if ( sumMap.containsKey(curSum - sum)) {
maxLen = Math.max(level - sumMap.get(curSum - sum), maxLen);
}
maxLen = preOrder(root.left, sum, curSum, level + 1, maxLen, sumMap);
maxLen = preOrder(root.right, sum, curSum, level + 1, maxLen, sumMap);
// 如果curSum这个累加和的记录是在遍历到cur时加上去的,那么在返回cur的父节点之前需要删除这个记录,
// 否则就不是从cur的父节点到cur的父节点的其他子节点,而是从cur到cur的兄弟节点
if (level == sumMap.get(curSum)) sumMap.remove(curSum);
return maxLen;
}

  二、在二叉树中找到两个节点的最近公共祖先

  1.给定一棵二叉树以及这棵树的两个节点o1和o2,返回o1和o2的最近公共祖先节点。

               1
/ \
2 3
/ \ / \
4 5 6 7
/
8 4和5的最近公共祖先为2,5和2的最近公共祖先为2,6和8的最近公共祖先为3,5和8的最近公共祖先为1

  解法:后序遍历二叉树,假设遍历到的当前节点为cur,因为后序遍历要先处理cur的两棵子树,假设处理cur左子树时返回节点为left,处理右子树时返回节点为right

  (1)如果cur等于null或者o1或者o2,返回cur

  (2)如果left和right都为空,说明cur整棵子树上都没有发现过o1和o2,返回null

  (3)如果left和right都不为空,说明左子树上发现过o1或o2,右子树上也发现过o2或o1,说明o1向上与o2向上的过程中,首次在cur相遇,返回cur

  (4)如果left和right有一个为空,有一个不为空,假设不为空的节点记为node,此时有两种可能,要么node是o1或o2中的一个,要么node已经是o1和o2的最近公共祖先,两种情况下,都可以返回node。

  执行过程是:

假设o1=6,o2=8
依次遍历4,5,2都没有发现o1或o2,所以1的左子树返回null
遍历6,发现等于o1,返回6,所以3的左子树返回6
遍历8,发现等于o2,返回8,所以7的左子树返回8
结点7的右子树为null,而左子树为8,所以返回8
遍历3,左子树返回6,右子树返回8,此时返回3
遍历1,左子树返回null,右子树返回3,因此返回3

  代码实现:

    public Node lowestAncestor(Node root, Node o1, Node o2) {
// 情况1:root为null时返回null,情况2:root为o1或者o2本身时,返回root
if (root == null || root == o1 || root == o2) return root;
Node left = lowestAncestor(root.left, o1, o2);
Node right = lowestAncestor(root.right, o1, o2);
// 情况3,在左子树发现了o1或o2,在右子树发现了o1或o2,那么最近公共祖先一定是root,返回root
if (left != null && right != null) return root;
// 情况4,left和right如果都为null,那么返回null
// 情况5:left和right有且只有一个不为null,那么返回不为null的那个
return left != null ? left : right;
}

  2.

  3.  

  三、求二叉树节点间的最大距离

  问题:从二叉树的节点A出发,沿途的节点只能经过一次,当到达节点B时,路径上的节点数叫做A到B的距离,包括A和B本身。

  思路:一个以root为根节点的树,最大距离只能来自以下三种情况:(1)root的左子树上的最大距离(2)root的右子树上的最大距离(3)root左子树上例root.left最远的距离 + 1 + root的右子树上离root.right最远的距离

  解法:1.整个过程为后序遍历。2.假设子树根节点为root,处理root左子树,得到两个信息,左子树上的最大距离记为lMax,左子树上距离root左孩子的最远距离记为maxfromLeft。同理,右子树上最大距离为rMax,右子树上距离root右孩子最远距离记为maxFromRight。那么maxFromLeft + 1 + maxFromRight就是跨root节点的最大距离,再与lMax和rMax比较,把三者中最大值作为root树上最大距离返回,maxFromLeft+1就是root左子树上离root最远的点到root的距离,maxFromRight+1就是root右子树上离root最远的点到root的距离,选两者中最大的一个作为root树上距离root最远的距离返回。

  执行过程是:

               1
/ \
2 3
/ /4 6
遍历4,lMax=0,record[0]=0,rMax=0,record[0]=0,curNodeMax=1,record[0]=1,返回1
遍历2,lMax=1,record[0]=1,rMax=0,record[0]=0,curNodeMax=2,record[0]=2,返回2
遍历6,lMax=0,record[0]=0,rMax=0,record[0]=0,curNodeMax=1,record[0]=1,返回1
遍历3,lMax=1,record[0]=1,rMax=0,record[0]=0,curNodeMax=2,record[0]=2,返回2
遍历1,lMax=2,record[0]=2,rMax=2,record[0]=2,curNOdeMax=5,record[0]=3,返回5,结束

  代码实现:

    public int maxDistance(Node root) {
int[] record = new int[1];
return postOrder(root, record);
} public int postOrder(Node root, int[] record) {
if (root == null) {
record[0] = 0;
return 0;
}
int lMax = postOrder(root.left , record);   // 左子树上两点最远距离
int maxFromLeft = record[0];   // 到root.left最远距离
int rMax = postOrder(root.right, record);   // 右子树上两点最远距离
int maxFromRight = record[0];   // 到root.right最远距离
int curNodeMax = maxFromLeft + 1 + maxFromRight;    // 经过root的最远距离
record[0] = Math.max(maxFromLeft, maxFromRight) + 1;    // 到root的最远距离
return Math.max(Math.max(lMax, rMax), curNodeMax); // 返回以root为根节点的树的两节点的最大距离
}

  四、求根节点到叶节点的路径

              -3
/ \
3 -9
/ \ / \
1 0 2 1
/\ 1 6 输出:[-3->3->1, -3->3->0->1, -3->3->0->6, -3->-9->2, -3->-9->1]

  分析过程:前序遍历,使用两个全局变量List<String> res, String str,其中res作为结果集list,str作为字符串。

1.""开始
2.-3->,遍历到-3
3.-3->3,遍历到3
4.-3->3->1->,遍历到1,打印
5.-3->3->0->1->遍历到1,打印
6.-3->3->0->6->遍历到6,打印
7.-3->3->0->遍历回到0
8.-3->3->遍历回到3
9.-3->-9->2->遍历到2,打印
10.-3->-9->1->遍历到1,打印
11.-3->-9->遍历回到-9
12.-3->遍历回到-3
13."" 结束

  代码实现:

    public List<String> binaryTreePaths(TreeNode root) {
List<String> res = new ArrayList<>();
preOrder(root, res, "");
return res;
} private void preOrder(TreeNode root, List<String> res, String str) {
if (root == null) return ;
str += root.val + "->";
preOrder(root.left , res, str);
preOrder(root.right, res, str);
if (root.left == null && root.right == null) {
str = str.substring(0, str.length() - 2);
res.add(str);
}
}

  变体:求是否存在从根节点到叶节点的节点和等于给定的值sum的路径

    public boolean hasPathSum(TreeNode root, int sum) {
boolean[] res = new boolean[1];
preOrder(root, sum, 0, res);
return res[0];
} private void preOrder(TreeNode root, int sum, int tmp, boolean[] res) {
if (root == null) return;
tmp += root.val;
preOrder(root.left, sum, tmp, res);
preOrder(root.right, sum, tmp, res);
if (root.left == null && root.right == null && tmp == sum) {
res[0] = true;
return ;
}
}

  变体:求一棵树上从上到下的路径数,这个路径满足节点的累加和等于给定值sum(需要注意pathSum还要自循环)

    public int pathSum(TreeNode root, int sum) {
if (root == null) return 0;
int[] count = new int[1];
preOrder(root, sum, 0, count);
return count[0] + pathSum(root.left, sum) + pathSum(root.right, sum);
} private void preOrder(TreeNode root, int sum, int tmp, int[] count) {
if (root == null) return ;
tmp += root.val;
preOrder(root.left , sum, tmp, count);
preOrder(root.right, sum, tmp, count);
if (tmp == sum) {
count[0]++;
}
}

OptimalSolution(2)--二叉树问题(3)Path路径问题的更多相关文章

  1. [Swift]LeetCode124. 二叉树中的最大路径和 | Binary Tree Maximum Path Sum

    Given a non-empty binary tree, find the maximum path sum. For this problem, a path is defined as any ...

  2. 【1】[leetcode-124] 二叉树中的最大路径和

    (没做出来,典型题目重要) 二叉树中的最大路径和(hard) 给定一个非空二叉树,返回其最大路径和. 本题中,路径被定义为一条从树中任意节点出发,达到任意节点的序列.该路径至少包含一个节点,且不一定经 ...

  3. Linux下修改PATH路径

    1.#PATH=$PATH:/opt/lamp/mysql/bin       使用这种方法,只对当前会话有效,也就是说每当登出或注销系统以后,PATH 设置就会失效 2.#vi /etc/profi ...

  4. Linux系统下修改环境变量PATH路径的三种方法

    这里介绍Linux的知识,比如把/etc/apache/bin目录添加到PATH中有三种方法,看完之后你将学会Linux系统下如何修改环境变量PATH路径,需要的朋友可以参考下 电脑中必不可少的就是操 ...

  5. 如何修改Window系统下PATH路径以及win8下masm32V11

    如何修改Window系统下PATH路径   //其实这个都是临时性的, 退出dos窗口就没有用了,只是做个笔记罢了   C:\Users\Administrator>    set path=E ...

  6. [转]sudo找不到命令:修改sudo的PATH路径

    sudo有时候会出现找不到命令,而明明PATH路径下包含该命令,让人疑惑.其实出现这种情况的原因,主要是因为当 sudo以管理权限执行命令的时候,linux将PATH环境变量进行了重置,当然这主要是因 ...

  7. 刚开始学java和刚去工作的时候,1.path路径 2.classpath路径 还有JAVA_HOME相当于/dgs这个路径

    把里面bin文件夹下面的可执行文件都配置到path路径下了,以后只要在Dos窗口输入命令就可以运行 无论是在dos窗口下还是在eclispe中只需要配置这个path变量,不需要配置classpath ...

  8. 沿着path路径做动画

    沿着path路径做动画 路径 效果 源码 // // ViewController.m // PathAnimation // // Created by YouXianMing on 16/1/26 ...

  9. Python之os.path路径模块中的操作方法总结

    #os.path模块主要集成了针对路径文件夹的操作功能,这里我们就来看一下Python中的os.path路径模块中的操作方法总结,需要的朋友可以参考下 解析路径路径解析依赖与os中定义的一些变量: o ...

  10. svg(1) path路径

    注: 笔记来自于http://www.jb51.net/html5/72250.html  以及http://blog.csdn.net/u013291076/article/details/2707 ...

随机推荐

  1. AJAX基础内容

    1.什么是ajax?为什么要使用ajax? ajax是Asynchronous JavaScript and XML ,也称为创建交互式网页应用开发技术. 2.为什么采用ajax 1)通过异步交互,提 ...

  2. idea 自动生成并跳转单元测试

    在要测试的类上按快捷键ctrl + shift + t,选择Create New Test,在出现的对话框的下面member内勾选要测试的方法,点击ok 或者点击菜单栏Navigate–>tes ...

  3. java架构之路-(JVM优化与原理)JVM类的加载机制

    话不多说,先上图. ***.class文件执行大概就是这样来走的.我们都知道我们的java文件经过编译以后会生成对应的class文件.先经过类装载子系统,然后塞进运行时内存模型的元空间,开始执行方法, ...

  4. docker 使用及基本命令

    一.docker简单使用 a.列出镜像 docker images b.从docker hub拉取最新版本镜像 docker pull xxx 错误: Error response from daem ...

  5. PHP 通过 ReflectionMethod 反射类方法获取注释返回 false 的问题解决

    php 通过反射 ReflectionMethod 类来获取类方法的相关信息,其中就包含方法的注释内容. 问题描述 在公司测试环境运行以下代码,如果是 cli 命令行模式运行,正常输出代码注释.如果是 ...

  6. Ionic2优于Ionic1的6个理由

    经历了一个从0到有的app的开发,我已经很熟悉Ionic1了,在此期间我曾发现过Ionic1的一些bug,和一些不合理的地方(根基版本 不同,后续我会陆续发表这些文章),我甚至在此期间对Ionic1进 ...

  7. 网页布局——float浮动布局

    我的主要参考资料是[Object object]的文章 float 布局应该是目前各大网站用的最多的一种布局方式了,但是也特别复杂,这里详细讲一下 首先,什么是浮动? 浮动元素是脱离文档流的,但不脱离 ...

  8. 云计算之走进LINUX(一)

    引言 小比特的随笔: 亲爱的博友所有随笔部分记录的是小比特的一些学习笔记,阅读性不是太强仅供有基础的博友参考,对小白来说阅读起来可能会有些吃力.当然也可以参考啦!小比特将在文章部分提供详细的内容介绍供 ...

  9. [JOJZ]3855.选择困难症

    [问题描述]又到吃饭时间,Polo 面对饭堂里琳(fei)琅(chang)满(keng)目(die)的各种食品,又陷入了痛苦的抉择中:该是吃手(jiao)打肉饼好呢,还是吃豆(cai)角(chong) ...

  10. [WPF自定义控件库] 模仿UWP的ProgressRing

    1. 为什么需要ProgressRing 虽然我认为这个控件库的控件需要模仿Aero2的外观,但总有例外,其中一个就是ProgressRing.ProgressRing是来自UWP的控件,部分代码参考 ...