文章目录
引言
- 二叉树的章节复习完了,今天把这个章节看完,刚好看到了二叉树展开为链表这个题目,似乎很象我在拼多多的复试题目!可惜了!多刷刷,当时实习就有着落了!
复习
新作
二叉树的右视图
注意
- 节点个数会为零,需要单独处理空节点
个人实现
- 这个题目相当于是一个递归遍历的过程,如存在右节点,首先返回有右节点,如果不存在右节点,先返回左节点
java
/**
* Definition for a binary tree node.
* public 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;
* }
* }
*/
class Solution {
List<Integer> list;
private void dfs(TreeNode root){
if(root == null) return;
list.add(root.val);
// 右节点不为空,优先遍历右节点
if(root.right != null) {
dfs(root.right);
return ;
}
// 右节点为空,遍历左节点
if(root.left != null){
dfs(root.left);
return ;
}
}
public List<Integer> rightSideView(TreeNode root) {
list = new ArrayList<>();
dfs(root);
return list;
}
}
不应该使用递归去解决这个问题,因为无法处理到最底下最左侧的节点,所以应该使用层序遍历尝试,只要是当前层最后一个节点,就是右视图可见的节点
java
/**
* Definition for a binary tree node.
* public 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;
* }
* }
*/
class Solution {
public List<Integer> rightSideView(TreeNode root) {
List<Integer> list = new ArrayList<>();
Queue<TreeNode> que = new ArrayDeque<>();
Queue<TreeNode> queTemp = new ArrayDeque<>();
if(root == null) return list;
que.offer(root);
while(!que.isEmpty()){
TreeNode node = que.poll();
if( que.isEmpty()) list.add(node.val);
if(node.left != null) queTemp.offer(node.left);
if(node.right != null) queTemp.offer(node.right);
// swap
if(!queTemp.isEmpty() && que.isEmpty()){
Queue<TreeNode> temp = que;
que = queTemp;
queTemp = temp;
}
}
return list;
}
}
这里只会使用两个队列,不知道怎么使用一个队列实现
下述是使用一个队列实现的BFS,本质上还是使用了两层循环,只不过第二层循环是控制次数的
参考实现
我的思路确实时间复杂度比较高,因为基本上就是遍历了整个树,我看题解,基本上只遍历了右视图能够看见的节点
深度优先搜索
思路描述
- 这里是使用dfs遍历整个地图实现的,每一次都是先左子节点,然后再是右子节点,然后使用一个字典维护最终的答案depth-val,因为最后总是右节点在最后,所以只要有深度,当前深度的value一定就是右子节点的值。
java
class Solution {
public List<Integer> rightSideView(TreeNode root) {
// 当前深度最右侧的节点的集合,key是深度,value是最右侧节点的值
Map<Integer, Integer> rightmostValueAtDepth = new HashMap<Integer, Integer>();
int max_depth = -1;
// 节点栈nodeStack 深度栈depthStack
Deque<TreeNode> nodeStack = new LinkedList<TreeNode>();
Deque<Integer> depthStack = new LinkedList<Integer>();
nodeStack.push(root);
depthStack.push(0);
while (!nodeStack.isEmpty()) {
TreeNode node = nodeStack.pop();
int depth = depthStack.pop();
if (node != null) {
// 维护二叉树的最大深度
max_depth = Math.max(max_depth, depth);
// 如果不存在对应深度的节点我们才插入
if (!rightmostValueAtDepth.containsKey(depth)) {
rightmostValueAtDepth.put(depth, node.val);
}
nodeStack.push(node.left);
nodeStack.push(node.right);
depthStack.push(depth + 1);
depthStack.push(depth + 1);
}
}
List<Integer> rightView = new ArrayList<Integer>();
for (int depth = 0; depth <= max_depth; depth++) {
rightView.add(rightmostValueAtDepth.get(depth));
}
return rightView;
}
}
实际上完全没有必要这样做,为什么不直接BFS?
二叉树展开为链表
- 题目链接
注意 - 节点数可能为空,需要特殊处理
- 使用right指向下一个节点,当作next
- 按照先序遍历的顺序展开
个人实现
哎,这个题目是拼多多第三面的主管面手撕的原题,当时没有理解题意,为什么不能像leetcode这样说明那?非得口述,很无语,不过很大一部分原因是我当时基础确实很薄弱!
思路分析
- 单纯使用递归应该是可以解决的,我只需要处理当前节点和其两个子节点的之间的关系,然后进行适当的转换就行!
- 使用tempNode保存右子节点
- 使用将左子节点转换到root的右指针就行
- 问题
- 遍历到最左侧的指针之后,我应该如何传到最顶端,接上右节点?可以试试看使用参数传递!
不对,没想好怎么做,更加直白的做法就是直接前序遍历,保存节点,然后逐个向后修改节点
java
/**
* Definition for a binary tree node.
* public 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;
* }
* }
*/
class Solution {
List<TreeNode> list;
void dfs(TreeNode root){
if(root == null ) return ;
// 保存右子节点
list.add(root);
dfs(root.left);
dfs(root.right);
}
public void flatten(TreeNode root) {
if(root == null ) return ;
list = new ArrayList<>();
dfs(root);
for(int i = 0;i < list.size() - 1;i ++){
list.get(i).left = null;
list.get(i).right = list.get(i + 1);
}
}
}
参考实现
具体分析
- 如果存在左子树,将左子树的右链条,插入到当前节点和右链的之间
- 否则,走到右儿子,执行相同的操作
个人想法
- 这里的想法和我想得很像,但是更加清晰,确定出了问题的具体的构成部分,然后在逐个实行,后续哪个部门不能实行,就重点关注那个部分!
- 还有就是我看问题的角度有问题,或者说思路区分度不够,实际上应该是看单个环节,一次看太多了,反而桎梏住了我的思路!
java
class Solution {
List<TreeNode> list;
void dfs(TreeNode root) {
if(root ==null) return;
TreeNode p = root.left;
if(p != null){
while(p.right != null) p = p.right;
p.right = root.right;
root.right = root.left;
root.left = null;
}
dfs(root.right);
}
public void flatten(TreeNode root) {
dfs(root);
}
}
这个思路要记住,记住了以后在遇到就不是问题了!
从前序与中序遍历序列构造二叉树
- 题目链接
注意 - 这里不会输出null,单纯就是中序遍历和前序遍历的结果
个人实现
- 果真是又遇到了这个题目,以前本科学习二叉树的时候,不知道写了多少遍,但是现在还是空白,不知道怎么做!尴尬!
- 若干年前,第一次学习链接
- 若干年前,第二次学习链接
大概只能推出这样,没啥时间了,上午都在做题,得去看看面经了,下午好准备面试!
参考实现
- 先找根节点,根节点一定是前序遍历的第一个点,
- 然后在中序遍历中找到对应根节点的位置,根节点左边就是左子树,右边就是右子树
- 如此不断递归下去就行了!
为了保证能够快速找到中序遍历中每一个节点的位置,创建一个哈希表,然后在递归地建立整个树!
java
class Solution {
Map<Integer,Integer> map;
public TreeNode buildTree(int[] preorder, int[] inorder) {
map = new HashMap<>();
for(int i = 0;i < inorder.length;i ++) map.put(inorder[i],i);
return build(preorder,inorder,0,preorder.length - 1,0,inorder.length -1);
}
private TreeNode build(int[] preorder, int[] inorder,int pl,int pr,int inl,int inr){
if(pl>pr || inl > inr) return null;
TreeNode root = new TreeNode(preorder[pl]);
int idx = map.get(root.val);
int len = idx - inl - 1;
root.left = build(preorder,inorder,pl + 1,pl + 1 + len,inl,idx - 1);
root.right = build(preorder,inorder,pl + 1 + len + 1,pr,idx + 1,inr);
return root;
}
}
路径总和III
- 题目链接
注意 - 树可以是空的,所以需要特殊处理
个人实现
这个题目,感觉没什么好的思路,只能遍历每一条路径,然后判定累加和是否为目标值,但是有多种情况,怎么办?
- 先把问题简单化,如果能够计算出所有的子树中根节点的所有可能的和的情况应该是可以实现的!
- 简化之后实现如下,每次返回一个左右子树包含的路径值的字典就行了
java
class Solution {
int tarSum = 0;
int count = 0;
private Map<Long,Integer> dfs(TreeNode root){
Map<Long,Integer> map = new HashMap<>();
if(root == null ) return map;
long curVal = (long)root.val;
map.put(curVal,map.getOrDefault(curVal,0) + 1);
Map<Long,Integer> mapL = dfs(root.left);
for(long x:mapL.keySet()){
map.put((long)x + curVal,mapL.get((long)x) + map.getOrDefault((long)x + curVal,0));
}
Map<Long,Integer> mapR = dfs(root.right);
for(long x:mapR.keySet()){
map.put((long)x + curVal,mapR.get((long)x) + map.getOrDefault((long)x + curVal,0));
}
count += map.getOrDefault((long)tarSum,0);
return map;
}
public int pathSum(TreeNode root, int targetSum) {
tarSum = targetSum;
dfs(root);
return count;
}
}
参考实现
前缀和+哈希表
- 前缀和
- 从根节点开始的路径元素和
- 哈希表
- 使用哈希表统计前缀和出现的次数
- 从根节点到node的路径是s,那么找到了cnt[s - targetSum]个的符合要求的答案,加入路径就行了(有点懵!)
换一种方式就可以理解了
- 跟正常前缀序列和差不多,对于序列[a1,a2,a3,a5],对于a2到a5的序列和,你知道用a5的前缀和 - a1的前缀和,在二叉树一个道理
- 如果要求红色圈的路径,相当于黑色圈的路径 - 蓝色圈的路径
- 蓝色圈的路径和黑色圈的路径都是从根节点出发的前缀和
- 红色路径不是从根节点出发的前缀和路径,可以相减计算
- ax - ay = target ==》ax - target = ay
- 看看ay有几个就行了
具体回溯实现
- 看看ay有几个就行了
- 终止条件
- 当前节点为空
- 单次迭代内容
- 遍历所有子节点
- 更新并计算路径
- 计算当前节点下有多少可行的路径
- 遍历所有子节点
clike
class Solution {
int count = 0;
public void dfs(TreeNode root,Long len, Map<Long,Integer> map,int targetSum){
if(root == null) return;
// 单次迭代内容
Long lenCur = len + root.val;
count += map.getOrDefault(lenCur - targetSum,0);
map.merge(lenCur,1,Integer::sum);
// 遍历左右子节点
dfs(root.left,lenCur,map,targetSum);
dfs(root.right,lenCur,map,targetSum);
map.merge(lenCur,-1,Integer::sum);
}
public int pathSum(TreeNode root, int targetSum) {
Map<Long,Integer> map = new HashMap<>();
map.put(0L,1);
Long len = 0L;
dfs(root,len,map,targetSum);
return count;
}
}
总结
merge方法补充
- 这里使用了merge,相当于put(key,getOrDefault(key,0) + 1);
- merge(key,1,Integer::sum) 如果不存在,key对应value就是1,如果存在就是在原来的值上再加1
左右子树的隔离性
同一个根节点之间,左右两个子树,不会相互成为彼此的前缀路径,所以需要还原现场!
二叉树的最近公共祖先
注意
- 可以使用int类型保存数据,int最多是10的10次方
个人实现
- 又是过去的回忆,忽然袭来的一天,笑死了,还是不知道怎么做,又遇到了,但是居然在我的博客里搜出来了,本科的时候做过!
- 若干年前,第一次学习记录
思路分析
- 先从简单的角度出发,先找到节点,然后保存下来节点的到达路径。
- 然后再比对一下这两个路径集合最后的相似点就行了!
clike
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
boolean findNode(TreeNode root, TreeNode tar,List<TreeNode> list){
if(root == null) return false;
if(root == tar) {
list.add(root);
return true;
}
// 判读是在左子树还是右子树
if(findNode(root.left,tar,list)){
// 左子树
list.add(root);
return true;
}else if(findNode(root.right,tar,list)){
list.add(root);
return true;
}else
return false;
}
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
List<TreeNode> listP = new ArrayList<>();
findNode(root,p,listP);
Collections.reverse(listP);
for(TreeNode s:listP) System.out.print(s.val);
System.out.println();
List<TreeNode> listQ = new ArrayList<>();
findNode(root,q,listQ);
Collections.reverse(listQ);
for(TreeNode s:listQ) System.out.print(s.val);
int i = 0;
for(;i < Math.min(listP.size(),listQ.size());i ++){
if(listP.get(i) != listQ.get(i)) break;
}
return listP.get(i - 1);
}
}
总结
- 感觉操作起来比较慢,有冗余的操作,其实再找任何一个节点的时候,都会将整个二叉树给遍历一遍,然后可以同时找到两个节点,没有必要一个树遍历一次,这样效果反而会很差!
参考实现
- 这里还是现针对单个节点层开始讨论的,将问题拆分成一个一个步骤,然后的在分析单个步骤是如何解决的,具体如下。
分类讨论
- 当前节点是空节点**==》**直接返回
- 当前节点是p **==》**直接返回
- 当前节点是q **==》**直接返回
- 其他
- 左子树找到了p和q:返回左子树的递归结果
- 右子树找到了p和q:返回左子树的递归结果
- 左右子树分别都着了q和p:返回当前节点
java
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
// 当前节点是q和p其中某一个
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 root;
}
return left != null? left:right;
}
}
总结
- 其实这里和我单个进行搜索目标节点的过程很相似,只不过变成了搜索两个节点,然后根据特殊情况进行返回吧!
总结
- 看了一下查灵山艾府大佬的相关总结经验,感觉还是很厉害的!还有就是刷题的话,还是没有一个固定固定的思路,还是不得行!没有一个指导的方式!
- 至此树已经刷完了,剩下的就剩下每一个章节的hard题目了,感觉还是要在刷第二遍!秋招加油!