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

  给定一棵二叉树和一个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. spring bean的生命周期与springmvc的生命周期

    配置在Spring中的Bean在Spring容器中从加载到销毁会经历那些过程呢?如果实现一些特定的Spring接口,这些特定接口的方法会在什么时候被调用呢?本文简单介绍一下这些过程. Bean在Spr ...

  2. Java Map知识点

    1.遍历 java遍历Map的方式有多种,一下以代码示例来说明使用: Map<String, String> tmap = new HashMap<String, String> ...

  3. jQuery常用方法(五)-jQuery CSS

    JQuery CSS 方法说明 css( name ) 访问第一个匹配元素的样式属性. css( properties ) 把一个"名/值对"对象设置为所有匹配元素的样式属性. $ ...

  4. selenium实现百度图片爬取

    因为是百度图片是瀑布流ajax异步上传的数据,所以这里用到抓包工具来抓取链接(fiddler) 好了直接上代码, from selenium import webdriver from seleniu ...

  5. golang学习之路

    目录 go语言介绍 开发环境准备 go语言基础 Go语言常用标准库 数据库相关 前端相关 web开发 go语言介绍 为什么要学习go语言 开发环境准备 从零开始搭建Go语言开发环境 VS Code配置 ...

  6. 虚拟机安装Centos7系统后优化操作

    重点说明 以下操作针对于VMware软件上新创建的Centos7的虚拟机的优化,当需要多台虚拟机的实验环境时,可通过以下需求先操作配置出一台优化机(也可称为模板机),并创建快照记录,以后的多台虚拟机环 ...

  7. 视频转换器 Wondershare Video Converter Ultimate v11.5.1 中文便携版

    Wondershare Video Converter Ultimate 是万兴公司出品的一款多功能音视频转换.DVD 刻录软件.视频下载软件.有了它,您可以随时随地观看.下载.编辑.转换.刻录视频, ...

  8. 【JAVA基础】JAVA基础语法

    1.1 Java语言概述什么是Java语言Java语言是美国Sun公司(Stanford University Network),在1995年推出的高级的编程语言. Java语言发展历史1995年Su ...

  9. 在chrome浏览器中调用IE浏览器并访问(openIE.reg自定义协议)

    在谷歌浏览器中有4种方法调用IE浏览器.如下: c++ socket通过浏览器在ie中打开指定url : vb生成exe,url访问exe启动ie并打开指定url : 通过socket实现通过http ...

  10. e课表项目第二次冲刺周期第一天

    昨天干了什么? 昨天与我们小组的成员商量了一个重大的决定,由于我们第一次冲刺周期的成果,就是我们决定我们要转型发展. 今天干了什么? 查阅相关的资料,我们正式决定要做一款学习的课程表APP,把简易作为 ...