在二叉树中查找等于目标和的前缀和的迭代解决方案

问题描述 投票:0回答:1

我无法为以下问题提出迭代解决方案(即不递归或使用调用堆栈)。因此,请向这里的社区寻求您的帮助。 :)

问题 给定二叉树的根和整数 targetSum,返回路径上的值之和等于 targetSum 的路径数。该路径不需要从根或叶开始或结束,但必须向下(即仅从父节点到子节点)。

例如: 根 = [10,5,-3,3,2,null,11,3,-2,null,1],总和 = 8

      10
     /  \
    5   -3
   / \    \
  3   2   11
 / \   \
3  -2   1

输入:root = [10,5,-3,3,2,null,11,3,-2,null,1], targetSum = 8 输出:3 说明:显示总和为 8 的路径。

其他测试用例:

  • PASS 输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1],targetSum = 22,预期输出:3
  • PASS 输入:root = [-1,-2,-3],targetSum = -1,预期输出:1
  • PASS 输入:root = [1,2,-3],targetSum = -1,预期输出:1
  • PASS 输入:root = [1,-2,-3,1,3,-2,null,-1],targetSum = 3,预期输出:1
  • PASS 输入:root = [1,2],targetSum = 2,预期输出:1
  • PASS 输入:root = [1,2,null],targetSum = 2,预期输出:1
  • 失败输入:root = [-2,null,-3],targetSum = -3,输出:0,预期输出:1

失败的测试用例图片:

  -2
   \
   -3

      

以上所有测试均通过,但最后一个测试失败。我的代码返回 0,但预期输出为 1,因为有一个节点本身的值为 -3,并且等于 targetSum,因此等于 targetSum 的节点数应等于 1。

我很难修复我的解决方案,使其适用于最后一个测试用例,同时确保其他测试用例都不会失败。

我的迭代解决方案:

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @param {number} targetSum
 * @return {number}
 */
var pathSum = function(root, targetSum) {
    let count = 0, cache = new Map();
    let stack = [[root, 0]];
    while (stack.length) {
        let [node, currSum] = stack.pop();
        if (node) {
            currSum += node.val;
            if (currSum === targetSum) {
                count++;
            }
            count += cache.get(currSum-targetSum) || 0;
            cache.set(currSum, (cache.get(currSum) || 0) + 1);
            stack.push([node.right, currSum]);
            stack.push([node.left, currSum]);

            if (!node.left) {
                cache.set(currSum, cache.get(currSum) - 1);
            }
        }
        
    }
    return count;
};

我已经有一个使用递归进行遍历的工作解决方案,但这个问题仅适用于基于迭代遍历的解决方案。我觉得当我们使用堆栈迭代遍历二叉树时,我错过了有关回溯如何工作的一些内容。如果您发现任何错误,请告诉我!

binary-tree depth-first-search backtracking tree-traversal
1个回答
0
投票

问题是您没有一个好的方法来知道何时从缓存中“删除”计数。在您执行删除计数的地方,它在添加计数后立即跟随,这是不正确的。其间发生的 push 指令并没有真正改变这一事实。如果在这种情况下(

!node.left
)您一开始就没有添加计数,情况会是一样的。
这更难以跟踪。

我建议添加另一个结构来跟踪

在哪个树深度

将计数添加到缓存中。然后,当您弹出具有一定深度的节点时,您知道应该删除任何大于深度的缓存。 这是您根据该想法改编的代码,我没有对其进行不必要的更改。评论指出了我所做的更改:

var pathSum = function(root, targetSum) { let count = 0, cache = new Map(); const depthLog = []; // Keep track of cache actions per depth (index in array) let stack = [[root, 0, 0]]; // Add the depth of the node as 3rd member while (stack.length) { let [node, currSum, depth] = stack.pop(); // Expect the 3rd member if (node) { // Remove cache that is no longer relevant while (depthLog.length > depth) { for (const sum of depthLog.pop()) { cache.set(sum, cache.get(sum) - 1); } } // currSum += node.val; if (currSum === targetSum) { count++; } count += cache.get(currSum-targetSum) || 0; cache.set(currSum, (cache.get(currSum) || 0) + 1); (depthLog[depth] ??= []).push(currSum); // Also log the act of caching this sum stack.push([node.right, currSum, depth+1]); // Add depth stack.push([node.left, currSum, depth+1]); // ... // Removed the `if` block at this place } } return count; };

此扩展不会影响时间复杂度:缓存删除的复杂度与添加计数的复杂度相同(只能删除之前添加的内容)。

© www.soinside.com 2019 - 2024. All rights reserved.