程序员面试金典 --- 4.12求和路径

这篇博客介绍了如何在二叉树中寻找节点值之和等于给定值的路径,主要讨论了两种方法:蛮力法和优化解法。优化解法利用了散列表来避免重复计算,提高了效率。


题目:求和路径

给定一棵二叉树,其中每个节点都含有一个整数数量(该值或正或负)。设计一个算法,打印节点数值总和等于某个给定值的所有路径(求路径数量!)。注意,路径不一定非得从二叉树得根节点或叶节点开始或结束,但是某方向必须向下(只能从父节点指向子节点方向)。

一、思路

1.先用蛮力法解决,只需要查看所有可能得路径。遍历每个节点,用递归法尝试所有向下的路径。并随着递归的进行跟踪路径的和。每当发现路径和等于目标和,答案就+1;用时O(NlogN) 每个点遍历完需要LogN。
2.尝试在蛮力法上优化,蛮力法有很多重复计算。对于像10→5→3→-2这样的路径,遍历10为起点时 5,3,-2会被遍历,遍历5为起点时 3,-2会被遍历,所有3,-2会被重复遍历,理想情况下,我们希望能够对该过程进行重用。
假设,对于数组(10,5,1,2,-1,-1,7,1,2)中,有多少连续的子序列相加等于目标和,比如8.
我们可以转换问题求解,对于每个元素y,我们试图找到符合下面描述的x的值?

如果数组中的每个元素都知道路径的行程和,那么求解问题就容易很多,只需要使用如下的等式,即 r u n n i n g S u m x = r u n n i n g S u m y − t a r g e t S u m runningSum_x =runningSum_y - targetSum runningSumx=runningSumytargetSum 。之后,我们只需要找出满足此公式的x的值即可。
在这里插入图片描述

建立一个键为 r u n n i n g S u m runningSum runningSum,其值为 r u n n i n g S u m runningSum runningSum出现的次数。之后,对于每一个元素y,我们散列表中以 r u n n i n g S u m y − t a r g e r S u m runningSum_y-targerSum runningSumytargerSum为键进行查找。散列表返回的值则表示为终止于元素y,且路径总和为 t a r g e t S u m targetSum targetSum的路径数量
例如:
在这里插入图片描述
r u n n i n g S u m 7 runningSum_7 runningSum7的值为24.如果 t a r g e t S u m targetSum targetSum的值是8,那么我们需要在散列表中查找键16,该键返回的值为2(有2个键为16的值,即数组索引为2开始和索引5开始的路径)。如上所示,索引3至索引7 和 索引3至索引7两条路径的和为8。
算法描述:
使用深度优化查找对树进行遍历。当我们访问每个节点时,执行一下操作

  1. 跟踪 r u n n i n g S u m runningSum runningSum的值。我们将使该变量成为函数的一个参数,并对其增加 node.value。
  2. 在散列表中查找 r u n n i n g S u m − t a r g e t S u m runningSum - targetSum runningSumtargetSum。我们从散列表获得的值为路径的总数。将变量 totalPaths 的值设置为该值
  3. 如果 r u n n i n g S u m = = t a r g e t S u m runningSum == targetSum runningSum==targetSum,则发现了另外一条从根节点开始的路径。将变量 totalPaths加 1
  4. r u n n i n g S u m runningSum runningSum加入到散列表中(如果 r u n n i n g S u m runningSum runningSum已经存在,则将增加其值)
  5. 对左子树和右子树进行递归,计算和为 targetSum 的路径的条数
  6. 对左子树和右子树的递归调用结束后,减少散列表 r u n n i n g S u m runningSum runningSum 对应的值。这是算法中进行回溯的过程,它恢复了前述步骤对散列表的修改,以便其他节点不受影响(这是因为我们已经完成了对当前节点的处理)

二、解法

1.蛮力法

int pathSum(TreeNode* root, int sum) {
	if(root == NULL)return 0;
	int pathRoot = countPathsWithSumFromNode(root, 0, sum);
	int pathLeft = pathSum(root->left, sum);
	int pathright = pathSum(root->right, sum);
	return pathRoot + pathLeft + pathright;
}
int countPathsWithSumFromNode(TreeNode *node, int curSum, int targetNum){
    if(node == NULL) return 0;
    curSum += node->val;
    int totalPaths = 0;
    if(curSum == targetNum){
        return totalPaths++;
    }
    totalPaths += countPathsWithSumFromNode(node->left, curSum, targetNum);
    totalPaths += countPathsWithSumFromNode(node->right, curSum, targetNum);
    return totalPaths;
}

2.优化解法1

int pathSum(TreeNode* root, int sum) {
	unordered_map<int, int> pathCount;
	return countPathWithSum(root, sum, 0, pathCount);
}
   
int countPathWithSum(TreeNode *node, int targetSum, int runningSum, unordered_map<int, int> &pathCount){
    if(node == NULL) return 0;
    runningSum += node->val;
    int sum = runningSum - targetSum;
    int totalPaths = 0;
    if(pathCount.count(sum)){
        totalPaths = pathCount[sum];
    }
    if(runningSum == targetSum){//发现从根节点到此点的路径和为目标值
        totalPaths++;
    }
    incrementHashTable(pathCount, runningSum, 1);
    totalPaths += countPathWithSum(node->left, targetSum, runningSum, pathCount);
    totalPaths += countPathWithSum(node->right, targetSum, runningSum, pathCount);
    incrementHashTable(pathCount, runningSum, -1);
    return totalPaths;
}
void incrementHashTable(unordered_map<int, int> &pathCount, int key, int delta){
    if(pathCount.count(key)){
        int newCount = pathCount[key] + delta;
        if(newCount == 0){
            pathCount.erase(key);
        }else{
            pathCount[key] = newCount;
        }
        return;
    }
    pathCount[key] = delta;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值