题目:输入一颗二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。
思路:这道题目较难,需要深入理解递归的精髓,多次理解。所谓路径是指从根结点开始到叶子结点的路径,中间的一段并不能叫作路径。要找出整棵树上路径和为指定值target的所有路径,显然需要遍历过全部的路径,去和如何遍历全部的路径,即将树上的所有路径都走一遍。如果人手动地来搜索和为target的路径,思路是:从根结点①开始,先向左经过②,然后经过④形成一条路径1;为了避免重复的经过①②结点,指针不再从①开始走第二条路径,而是将指针从④跳回②然后到⑤这样形成路径2;之后指针从⑤跳回②再跳回①,然后到达③,再到达⑥,然后从⑥跳回到③再跳到⑦,即每一次都是到达一个结点,然后再到达这个结点的左结点,再到达这个结点的右结点,当到达null时就表示一条路径遍历完成,然后return到上一个父结点然后到达它的右结点,这显然就是一个前序遍历的过程,即使用前序遍历的递归实现时,程序遍历二叉树结点的顺序就是我刚才扫描路径的顺序,因此可以对先序遍历的递归过程进行一些改变来遍历所有的路径。注意,这里并不是凭空构造出一个递归过程,而是利用前序遍历的过程恰好就是遍历路径的过程这一个特点,来使用先序遍历解决问题。需要在先序遍历的过程中记录每条路径的和,当和达到target是就将此路径保存到结果集中。
根据先序遍历的过程,中-左-右,先遍历最左端的路径①②④,当遇到边界条件即root==null时递归函数返回return到上一层②,然后开始遍历结点②的右子树,先遍历右子树的最左端路径之后逐渐向右边的路径遍历,即遍历得到①②⑤返回到②返回到①,之后得到路径①③⑥,然后返回到③遍历得到①③⑦,即递归中每一次return都是因为遍历到了null即遍历完了一条路径,即在先序遍历中,每次遍历完一条路径就进行一次return,除了遍历到null时会return到方法的调用处即上一个结点之外,当递归方法执行结束,例如遍历完右结点之后也会return,到上一个结点。
本题的解决方法就是跟着先序遍历的递归过程,还是按照先序遍历的递归过程来进行,只是在递归的同时记录一下路径的信息。
设计一个递归方法进行先序遍历,
1:先遍历中间结点,此时的操作是将此结点root加入到成员变量(或全局变量)list中list.add(root),使得当前经过的路径或者部分路径延伸到当前结点root(理解,递归的过程可以认为任意时刻只在研究一条路径,将这条路径用list来表示,于是访问的结点要添加到这个list上,return即更换路径后要将刚加上的结点取出),即list.add(root),然后将root.val加入到当前路径的和sum上面sum+=roo.val,然后判断当前的sum是否等于target。做判断:如果root.left和root.right都等于null,说明此时遍历到了叶子结点,即已经遍历到了路径的末尾,那么要判断sum与target是否相同,如果相等,说明这是一条符合要求的路径,于是此时的list就是一条所求的路径,新建resList=list,并将次resList存入结果集allList中即可。(注意,必须将list赋值给一个其他变量在再保存,因为list本身是公用的,之后会进行添加移除结点等操作,因此会造成根据引用传递会造成已经保存的list发生变化,于是不可行),结果保存完成之后,就可以return了,因为路径上的结点已经遍历光了,需要换一条路径了,但是在return之前,需要将list上面的结点root删除,因为要换一条路径了,另一条路径上是没有这个结点的。即不管这条路径是否是符合要求的路径,在方法返回之前都需要将新加上的结点删除。如果sum!=target表示这条路径虽然已经遍历完成了,但是不符合要求,于是对其进行return,与遍历另一条路径,在return之前也要将root结点移除,即list.remove(list.size()-1)注意list中结点的序号也是从i=0开始到i=size()-1.
2:之后说明root.left和root.right不全为null,于是还要接着向下遍历,根据先序遍历的过程,应该先遍历左子树,在遍历右子树,于是调用递归方法,此时需要遍历的子树是root.left,此时的list是添加了root之后的list,此时的已有的和是加上了root.val的sum,targer还是原来的target,结果集也还是原来的结果集。
即先后调用process(root.left,list,sum)和process(root.right,list,sum)。
递归方法process(root,list,sum)的含义功能是:对于根结点为root的二叉树,当前遍历root之前的路径和是sum,遍历之前的路径是list,遍历root树,遍历出root树上面和为target-sum的路径,每一条路径在遍历时的临时路径为list,每遍历出一条就将其放入到结果集allList中。
注意:process(root.left,list,sum)和process(root.right,list,sum)是并列的关系,功能是相同的,就是将root左右子树上面合适的路径逐条放入到allList中,process(root.left,list,sum)和process(root.right,list,sum)执行完成之后,说明当前的路径已经遍历完成了,于是不管这条路径是否是合格的路径,遍历完成之后都要返回,返回之前都必须删除刚添加的结点,list.remove(list.size()-1)??????????
我的代码:
F常识:这里将list所表示的临时路径保存到结果集allLists中时,要保证加入结果集的集合不会受到后序list的变化而变化。因此如果使用ArrayList<Integer> tempList=list,allLists.add(list);这样是错误的,由于参数传递,tempList与list实际指向的是同一个地址,于是当之后list发生变化时tempList也会发生变化(对于基本数据类型,int等,取出之后原变量的修改不会造成取出变量的联动变化,但是这里是ArrayList是对象类型,是引用传递的,会变化,因此要注意),因此这里应该新建一个ArrayList,使得它里面的元素和原来的结合的元素相同,因此使用ArrayList<> tempList=new ArrayList<>(list),这个构造方法表示建立一个与list有相同元素的新的集合对象,对象是独立的,只是里面的元素相同而已。import java.util.ArrayList;
//找出所有和为target的路径:递归,先序遍历的同时记录路径和信息,深入掌握递归
public class Solution {
//定义成员变量存放结果集
ArrayList<ArrayList<Integer>> allLists;
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
allLists=new ArrayList<ArrayList<Integer>>();
//特殊输入
if(root==null) return allLists;
//存放当前正在遍历的路径上的结点
ArrayList<Integer> list=new ArrayList<>();
//表示当前list上的结点的和
int sum=0;
//调用递归方法实现功能
this.process(root,list,sum,target);
//返回结果集
return allLists;
}
//递归方法,基于先序遍历,遍历的同时记录路径的和信息,灵活掌握递归的返回和展开
private void process(TreeNode root,ArrayList<Integer> list,int sum,int target){
//边界条件
if(root==null) return;
//将root和root.val加到当前的list上面
list.add(root.val);
sum+=root.val;
//当遍历到底时(即到达叶子结点)--当前路径遍历结束
if(root.left==null&&root.right==null){
//判断当前路径是否符合要求
if(sum==target){
//路径符合要求,加入结果集中
//常识:只能重建一个与list有相同元素的集合,不能将list赋值给一个集合变量,避免引用传递
ArrayList<Integer> tempList=new ArrayList<Integer>(list);
allLists.add(tempList);
}
//不管路径是否符合要求,当一条路径遍历结束后都要返回,返回前都要移除刚添加的结点root
list.remove(list.size()-1);
return;
}
//如果root.left==null和root.right==null不全为null,说明没有遍历到路径结尾,继续遍历
//遍历root的左子树,按照新的要求找路径
this.process(root.left,list,sum,target);
//遍历root的右子树,按照新的要求找路径
this.process(root.right,list,sum,target);
//??????千万注意,左右子树找完之后,要删除刚加入的结点??????
list.remove(list.size()-1);
}
}
参考代码:
import java.util.ArrayList;
public class Solution {
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
ArrayList<ArrayList<Integer>> arr=new ArrayList<ArrayList<Integer>>();
if(root==null)
return arr;
ArrayList<Integer> a1=new ArrayList<Integer>();
int sum=0;
pa(root,target,arr,a1,sum);
return arr;
}
public void pa(TreeNode root,int target,ArrayList<ArrayList<Integer>> arr, ArrayList<Integer> a1,int sum){
if(root==null)
return ;
sum+=root.val;
if(root.left==null&&root.right==null){
if(sum==target)
{ a1.add(root.val);
arr.add(new ArrayList<Integer>(a1));
a1.remove(a1.size()-1);
}
return ;
}
a1.add(root.val);
pa(root.left,target,arr,a1,sum);
pa(root.right,target,arr,a1,sum);
a1.remove(a1.size()-1);
}
}
最优代码:
public class Solution {
private ArrayList<ArrayList<Integer>> listAll = new ArrayList<ArrayList<Integer>>();
private ArrayList<Integer> list = new ArrayList<Integer>();
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
if(root == null) return listAll;
list.add(root.val);
target -= root.val;
if(target == 0 && root.left == null && root.right == null)
listAll.add(new ArrayList<Integer>(list));
FindPath(root.left, target);
FindPath(root.right, target);
list.remove(list.size()-1);
return listAll;
}
}