剑指Offer:二叉树中和为某一值的路径(一、二、三)全解。(二叉树相关知识应用)


本文介绍了剑指offer中“二叉树中和为某一路径”中三种变种题,题目中对于“路径”的定义不同,输出结果也不同,因此解题方法也不同。但是三种变体都可以用二叉树常规解法来求解,理清思路对于“回溯、迭代”之类的题也有指导意义,因此做记录总结为下。

1、JZ82二叉树中和为某一值的路径(一)

题目

给定一个二叉树root和一个值 sum ,判断是否有从根节点到叶子节点的节点值之和等于 sum 的路径。

  • 该题路径定义为从树的根结点开始往下一直到叶子结点所经过的结点
  • 叶子节点是指没有子节点的节点
  • 路径只能从父节点到子节点,不能从子节点到父节点
  • 总节点数目为n

代码:

import java.util.*;
public class Solution {
    private static int sumVal = -1;
    private boolean tag = false;
    public boolean hasPathSum (TreeNode root, int sum) {
        // 采用深度优先搜索就好了
        sumVal = sum;
        dfs(root, 0);
        return tag;
    }
    
    public void dfs(TreeNode root, int tmpSum){
        if(root == null){
            return;
        }
        
        if(root.left != null){
            dfs(root.left, tmpSum + root.val);
        }
        //叶子节点
        if(root.left == null & root.right == null){
            boolean tmpTag = tmpSum + root.val == sumVal;
            tag = tag || tmpTag;
        }
        
        if(root.right != null){
            dfs(root.right, tmpSum + root.val);
        }
    }
}

复杂度分析:

时间复杂度 O(N) : N 为二叉树的节点数,最差的情况递归所有节点。
空间复杂度 O(1) : 常数级变量空间

2、JZ34 二叉树中和为某一值的路径(二)

题目

输入一颗二叉树的根节点root和一个整数expectNumber,找出二叉树中结点值的和为expectNumber的所有路径。

  • 该题路径定义为从树的根结点开始往下一直到叶子结点所经过的结点
  • 叶子节点是指没有子节点的节点
  • 路径只能从父节点到子节点,不能从子节点到父节点
  • 总节点数目为n

示例
在这里插入图片描述

输入:{10,5,12,4,7},22
返回值:[[10,5,7],[10,12]]
说明:返回[[10,12],[10,5,7]]也是对的     

代码:

import java.util.ArrayList;

public class Solution {
    //难点在于路径是怎么记录的呢?
    private static int sums = -1;
    private static ArrayList<ArrayList<Integer>> ans = new ArrayList<ArrayList<Integer>>();
    
    public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int expectNumber) {
        sums = expectNumber;
        dfs(root, 0, new ArrayList<Integer>());
        return ans;
    }
    
    public void dfs(TreeNode root, int sumTmp, ArrayList<Integer> road){
        if(root == null){
            return;
        }
        road.add(root.val);
        
        if(root.left == null & root.right == null){
            if(sumTmp + root.val == sums){
            //这里需要新建一个,否则原来的road是会随着迭代变的
                ArrayList<Integer> tmp = new ArrayList<Integer>();
                for(int i = 0; i < road.size() ; i++){
                    int a = road.get(i).intValue();
                    tmp.add(new Integer(a));
                }
                ans.add(road);
            }
        }
        if(root.left != null){
            dfs(root.left, sumTmp + root.val, road)
        }
        if(root.right != null){
            dfs(root.right, sumTmp + root.val, road);
        }
        //注意这里的回溯,要恢复状态
        road.remove(road.size() - 1);
    }
}

复杂度分析:

时间复杂度:O(n)
空间复杂度:O(n)

3、JZ84 二叉树中和为某一值的路径(三)

题目

给定一个二叉树root和一个整数值 sum ,求该树有多少路径的的节点值之和等于 sum 。

  • 该题路径定义不需要从根节点开始,也不需要在叶子节点结束,但是一定是从父亲节点往下到孩子节点。
  • 总节点数目为n。
  • 保证最后返回的路径个数在整形范围内(即路径个数小于2的31次方)。

示例:
在这里插入图片描述

输入:{1,2,3,4,5,4,3,#,#,-1},6
返回值:3
说明:如图所示,有3条路径符合

代码:

方法一:暴力求解法:深度遍历每一个节点,再以每个节点为根节点深度遍历所有子节点

import java.util.*;

public class Solution {

    int key = 0;
    public int FindPath (TreeNode root, int sum) {
        // write code here
        if(null == root) return key;
        dfs(root, sum);
        FindPath(root.left, sum);
        FindPath(root.right, sum);
        return key;
    }
    void dfs(TreeNode root, int sum){
        if(null == root) return;
        sum -= root.val;
        if(sum == 0) key++;
        dfs(root.left, sum);
        dfs(root.right, sum);
    }
}

方法二:遍历每一个节点,其实就三种情况(参考博文

  • 自身是否满足(自身的值就是所求的路径长度)
  • 左节点满足数量
  • 右节点满足数量

三者相加就是以该节点出发遍历子节点获取到的合规路径总数量。

我们以一个节点为例,判断该节点路径数量的时候,存在两种场景。

  • 场景一:要带着parent节点判断是否满足。这时,sum求和值只能等于parentSum-root.val;
  • 场景二:不带parent节点判断是否满足。这时候就有就继续存在两种可能。
    1.不带parent节点继续判断是否等于sum
    2.带着parent节点判断是否等于sum,这时候sum=parentSum-root.val;

根节点时,默认肯定时不带parent节点判断,因为它没有parent节点。

import java.util.*;

public class Solution {
    public int FindPath (TreeNode root, int sum) {
        // write code here
        return FindPathByRoot(root, sum, false);
    }
    int FindPathByRoot(TreeNode root, int sum, boolean isHaveRootValue){
        if(root == null){
            return 0;
        }
        int cnt = 0;
        if(root.val == sum){
            cnt ++;
        }
        if(root.left != null){
            if(!isHaveRootValue){
                cnt += FindPathByRoot(root.left, sum, false);
            }
            cnt += FindPathByRoot(root.left, sum - root.val, true);
        }
        if(root.right != null){
            if(!isHaveRootValue){
                cnt += FindPathByRoot(root.right, sum, false);
            }
            cnt += FindPathByRoot(root.right, sum - root.val, true);
        }
        return cnt;
    }
}

方法三:我的理解是采用了一个类似前缀和思想来求解(比较巧妙)
采用了一个哈希表来记录(路径-个数)。其中“路径”表示从根节点出发到该节点未知的路径长度,“个数”表示的是这个路径长度下一共有几种不同的路径个数。这个定义就有点像前缀和。新考虑一个节点,就可以得到其到根节点之间的距离a,如果小于sum就添加进哈希表,如果大于sum就查找哈希表是不是存在截断路径(前缀路径长度等于a-sum),前缀路径只被计算一次,用空间换时间。
方法的处理需要回溯,因为考虑右节点时左节点产生的前缀路径就要删除,因此代码如下

import java.util.*;

public class Solution {
    Map<Integer, Integer> tag = new HashMap<Integer, Integer>(); //路径长度及条数
    public int FindPath (TreeNode root, int sum) {
        tag.put(0, 1); //初始时,路径和为0的有1条
        return dfs(root, sum, 0);
    }
    
    int dfs(TreeNode root, int sum, int last){
        int res = 0; //考虑该节点之后的路径总数
        if(root == null){
            return 0;
        }
        int tmp = last + root.val;  //到目前为止的累加和
        if(tag.containsKey(tmp - sum)){  //截断路径是不是存在
            res += tag.get(tmp - sum);   //存在的话就加上存在路径数
        }
        //添加该条路径
        tag.put(tmp, tag.getOrDefault(tmp, 0) + 1);
        //子节点
        res += dfs(root.left, sum, tmp);
        res += dfs(root.right, sum, tmp);
        //路径回退
        if(tag.get(tmp) == 1){
            tag.remove(tmp);
        }else{
            tag.put(tmp, tag.get(tmp) - 1);
        }
        return res;
    }
}

复杂度分析:

方法一:
时间复杂度:O(n^2)
空间复杂度:O(1)

方法二:
时间复杂度:O(n)
空间复杂度:O(n)

方法二:
时间复杂度:O(n)
空间复杂度:O(n)

后记:

焦虑之下,除了慢慢前行,似乎也没有其他路。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值