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

  给定一棵二叉树和一个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. electron教程(二): http服务器, ws服务器, 进程管理

    我的electron教程系列 electron教程(一): electron的安装和项目的创建 electron教程(二): http服务器, ws服务器, 进程管理 electron教程(三): 使 ...

  2. Linux下查看版本信息

    Linux下如何查看版本信息, 包括位数.版本信息以及CPU内核信息.CPU具体型号等.   1.# uname -a   (Linux查看版本当前操作系统内核信息)   2.# cat /proc/ ...

  3. 在创建activiti5..22所需的25张表时 ,所用的方法和遇到的问题。

    最近在学习关于activiti流程设计的相关内容,首先第一步就需要了解25张activiti相关的表,具体的每张表的含义 请自行百度. 这里讲一下 用java代码生成所需要的25张表,很简单: pub ...

  4. How to setup Electrum testnet mode and get BTC test coins

    For some reason we need to use BTC test coins, but how to set up the Bitcoin testnet wallet and get ...

  5. JS实现数组排序的方法

    前言 排序是计算机内经常进行的一种操作,其目的是将一组“无序”的记录序列调整为“有序”的记录序列,当然排序也是算法中的一种,javascript内置的sort函数是多种排序算法的集合,数组在原数组上进 ...

  6. 记一次arch滚挂后,更换lts内核

    背景 因为arch的滚动升级模式,每天pacman -Syu已经是一种习惯了(虽然我是使用yay的),升级过程中会连内核一起升级,但不会立刻生效,通常要等到下次重启时才会生效. 因为此前使用的是有一点 ...

  7. [scrapy-redis] 将scrapy爬虫改造成分布式爬虫 (2)

    1. 修改redis设置 redis默认处在protection mode, 修改/etc/redis.conf, protected-mode no, 或者给redis设置密码, 将bind 127 ...

  8. ELK 学习笔记之 Logstash之filter配置

    Logstash之filter: json filter: input{ stdin{ } } filter{ json{ source => "message" } } o ...

  9. 理解LSTM网络--Understanding LSTM Networks(翻译一篇colah's blog)

    colah的一篇讲解LSTM比较好的文章,翻译过来一起学习,原文地址:http://colah.github.io/posts/2015-08-Understanding-LSTMs/ ,Posted ...

  10. day 20作业

    目录 1.下面这段代码的输出结果将是什么?请解释. 2.多重继承的执行顺序,请解答以下输出结果是什么?并解释. 3.什么是新式类,什么是经典类,二者有什么区别?什么是深度优先,什么是广度优先? 4.用 ...