在二叉树中使用给定总和打印所有路径的算法

时间:2012-07-04 11:37:54

标签: binary-tree

以下是面试问题。

  

您将获得一个二叉树(不一定是BST),其中每个节点都包含一个值。   设计一种算法来打印总计到该值的所有路径。   请注意,它可以是树中的任何路径 - 它不必启动   在根。

虽然我能够在树中找到从根开始的所有路径都有给定的总和,但我无法对从不在根处开始的路径这样做。

18 个答案:

答案 0 :(得分:15)

嗯,这是一棵树,而不是图表。所以,你可以这样做:

伪代码:

global ResultList

function ProcessNode(CurrentNode, CurrentSum)
    CurrentSum+=CurrentNode->Value
    if (CurrentSum==SumYouAreLookingFor) AddNodeTo ResultList
    for all Children of CurrentNode
          ProcessNode(Child,CurrentSum)

好吧,这会为您提供从根开始的路径。但是,你可以做一个微小的改变:

    for all Children of CurrentNode
          ProcessNode(Child,CurrentSum)
          ProcessNode(Child,0)

您可能需要考虑一下(我忙于其他事情),但这应该基本上运行以树中每个节点为根的相同算法

编辑:这实际上只给出了“终端节点”。但是,由于这是一棵树,您可以从这些终端节点开始,然后向后走,直到获得所需的总和。

编辑2:当然,如果所有值都是正数,那么如果当前总和> =所需的值,则可以中止下降

答案 1 :(得分:10)

这是一个O(n + numResults)答案(与@ Somebody的答案基本相同,但解决了所有问题):

  1. 执行二叉树的预订,按顺序或后序遍历。
  2. 在进行遍历时,维护从根节点到当前节点上方节点的节点值的累积和。我们称这个值为cumulativeSumBeforeNode
  3. 当您访问遍历中的节点时,将其添加到密钥cumulativeSumBeforeNode的哈希表中(该键的值将是节点列表)。
  4. 计算cumulativeSumBeforeNode与目标总和之间的差异。在哈希表中查找这个差异。
  5. 如果哈希表查找成功,它应该生成一个节点列表。这些节点中的每一个都代表解决方案的起始节点。当前节点表示每个对应的起始节点的结束节点。将每个[开始节点,结束节点]组合添加到答案列表中。如果哈希表查找失败,则不执行任何操作。
  6. 当您完成遍历遍历中的节点后,从哈希表中密钥cumulativeSumBeforeNode中存储的列表中删除该节点。
  7. 代码:

    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    
    public class BinaryTreePathsWithSum {
        public static void main(String[] args) {
            BinaryTreeNode a = new BinaryTreeNode(5);
            BinaryTreeNode b = new BinaryTreeNode(16);
            BinaryTreeNode c = new BinaryTreeNode(16);
            BinaryTreeNode d = new BinaryTreeNode(4);
            BinaryTreeNode e = new BinaryTreeNode(19);
            BinaryTreeNode f = new BinaryTreeNode(2);
            BinaryTreeNode g = new BinaryTreeNode(15);
            BinaryTreeNode h = new BinaryTreeNode(91);
            BinaryTreeNode i = new BinaryTreeNode(8);
    
            BinaryTreeNode root = a;
            a.left = b;
            a.right = c;
            b.right = e;
            c.right = d;
            e.left = f;
            f.left = g;
            f.right = h;
            h.right = i;
    
            /*
                    5
                  /   \
                16     16
                  \     \
                  19     4
                  /
                 2
                / \
               15  91
                    \
                     8
            */
    
            List<BinaryTreePath> pathsWithSum = getBinaryTreePathsWithSum(root, 112); // 19 => 2 => 91
    
            System.out.println(Arrays.toString(pathsWithSum.toArray()));
        }
    
        public static List<BinaryTreePath> getBinaryTreePathsWithSum(BinaryTreeNode root, int sum) {
            if (root == null) {
                throw new IllegalArgumentException("Must pass non-null binary tree!");
            }
    
            List<BinaryTreePath> paths = new ArrayList<BinaryTreePath>();
            Map<Integer, List<BinaryTreeNode>> cumulativeSumMap = new HashMap<Integer, List<BinaryTreeNode>>();
    
            populateBinaryTreePathsWithSum(root, 0, cumulativeSumMap, sum, paths);
    
            return paths;
        }
    
        private static void populateBinaryTreePathsWithSum(BinaryTreeNode node, int cumulativeSumBeforeNode, Map<Integer, List<BinaryTreeNode>> cumulativeSumMap, int targetSum, List<BinaryTreePath> paths) {
            if (node == null) {
                return;
            }
    
            addToMap(cumulativeSumMap, cumulativeSumBeforeNode, node);
    
            int cumulativeSumIncludingNode = cumulativeSumBeforeNode + node.value;
            int sumToFind = cumulativeSumIncludingNode - targetSum;
    
            if (cumulativeSumMap.containsKey(sumToFind)) {
                List<BinaryTreeNode> candidatePathStartNodes = cumulativeSumMap.get(sumToFind);
    
                for (BinaryTreeNode pathStartNode : candidatePathStartNodes) {
                    paths.add(new BinaryTreePath(pathStartNode, node));
                }
            }
    
            populateBinaryTreePathsWithSum(node.left, cumulativeSumIncludingNode, cumulativeSumMap, targetSum, paths);
            populateBinaryTreePathsWithSum(node.right, cumulativeSumIncludingNode, cumulativeSumMap, targetSum, paths);
    
            removeFromMap(cumulativeSumMap, cumulativeSumBeforeNode);
        }
    
        private static void addToMap(Map<Integer, List<BinaryTreeNode>> cumulativeSumMap, int cumulativeSumBeforeNode, BinaryTreeNode node) {
            if (cumulativeSumMap.containsKey(cumulativeSumBeforeNode)) {
                List<BinaryTreeNode> nodes = cumulativeSumMap.get(cumulativeSumBeforeNode);
                nodes.add(node);
            } else {
                List<BinaryTreeNode> nodes = new ArrayList<BinaryTreeNode>();
                nodes.add(node);
                cumulativeSumMap.put(cumulativeSumBeforeNode, nodes);
            }
        }
    
        private static void removeFromMap(Map<Integer, List<BinaryTreeNode>> cumulativeSumMap, int cumulativeSumBeforeNode) {
            List<BinaryTreeNode> nodes = cumulativeSumMap.get(cumulativeSumBeforeNode);
            nodes.remove(nodes.size() - 1);
        }
    
        private static class BinaryTreeNode {
            public int value;
            public BinaryTreeNode left;
            public BinaryTreeNode right;
    
            public BinaryTreeNode(int value) {
                this.value = value;
            }
    
            public String toString() {
                return this.value + "";
            }
    
            public int hashCode() {
                return Integer.valueOf(this.value).hashCode();
            }
    
            public boolean equals(Object other) {
                return this == other;
            }
        }
    
        private static class BinaryTreePath {
            public BinaryTreeNode start;
            public BinaryTreeNode end;
    
            public BinaryTreePath(BinaryTreeNode start, BinaryTreeNode end) {
                this.start = start;
                this.end = end;
            }
    
            public String toString() {
                return this.start + " to " + this.end;
            }
        }
    }
    

答案 2 :(得分:9)

基于克里斯蒂安的答案:

public void printSums(Node n, int sum, int currentSum, String buffer) {
     if (n == null) {
         return;
     }
     int newSum = currentSum + n.val;
     String newBuffer = buffer + " " + n.val;
     if (newSum == sum) {
         System.out.println(newBuffer);
     }
     printSums(n.left, sum, newSum, newBuffer);
     printSums(n.right, sum, newSum, newBuffer);
     printSums(n.left, sum, 0, "");
     printSums(n.right, sum, 0, "");
} 

printSums(root, targetSum, 0, "");

答案 3 :(得分:3)

这是一种nlogn复杂度的方法。

  1. 按顺序遍历树。
  2. 同时维护所有节点以及Hashmap<CumulativeSum, reference to the corresponding node>中的累积总和。
  3. 现在在给定节点处计算从根到累积的总和,直到节点说这是SUM
  4. 现在在SUM-K
  5. 中查找值HashMap
  6. 如果条目存在,请使用HashMap
  7. 中的相应节点引用
  8. 现在我们有一个从节点引用到当前节点的有效路径。

答案 4 :(得分:3)

JAVA中的干净解决方案。使用内部递归调用跟踪遍历的路径。

private static void pathSunInternal(TreeNode root, int sum, List<List<Integer>> result, List<Integer> path){
    if(root == null)
        return;     
    path.add(root.val);
    if(sum == root.val && root.left == null && root.right == null){
        result.add(path);
    }

    else if(sum != root.val && root.left == null && root.right == null)
        return;
    else{
        List<Integer> leftPath = new ArrayList<>(path);
        List<Integer> rightPath = new ArrayList<>(path);
        pathSunInternal(root.left, sum - root.val, result, leftPath);
        pathSunInternal(root.right, sum - root.val, result, rightPath);
    }
}

public static List<List<Integer>> pathSum(TreeNode root, int sum) {
    List<List<Integer>> result = new ArrayList<>(); 
    List<Integer> path = new ArrayList<>();
    pathSunInternal(root, sum, result, path);       
    return result;
}

答案 5 :(得分:2)

更新:我现在看到我的回答没有直接回答你的问题。如果证明它有用,我会把它留在这里,但它不需要投票。如果没用,我会将其删除。我同意@nhahtdh,但是,当他建议“以所有其他节点重用你的算法作为root。”

有人怀疑面试官在这里钓鱼递归。不要让他失望!

给定一个节点,你的例程应该自己对每个子节点调用(如果有的话),然后将节点自己的数据添加到返回值,然后返回总和。

为了额外的功劳,请警告面试官你的例程可能会失败,如果在一般图形而不是二叉树上使用,则进入无底的无限递归。

答案 6 :(得分:1)

可以将此树缩减为加权图G,其中每个边权重=每个节点中的值的总和。

然后,在图G上运行Floyd-Warshall算法。通过检查结果矩阵中的元素,我们可以获得所有节点对,其中总和等于所需的总和。

另外,请注意算法提供的最短路径也是此树中2个节点之间的唯一路径。

这只是另一种方法,不像递归方法那样有效。

答案 7 :(得分:1)

我们可以用树结构动态编程来解决它,时间和空间复杂度都是O(n ^ 2),其中n是所有树节点的数量。

这个想法如下:

对于树节点,我们保留一组记录从u到其所有后代的所有可能总和。然后递归地,任何节点的集合可以由它的两个孩子更新,特别是通过合并两个孩子的集合。

伪代码是:

bool findPath(Node u, Set uset, int finalSum) {
    Set lset, rset;
    if (findPath(u.left, lset, finalSum) || findPath(u.right, rset, finalSum)) return true;
    for (int s1 : lset) {
        if (finalSum - u.val - s1 == 0 || rset.contains(finalSum - u.val - s1)) return true;
        // first condition means a path from u to some descendant in u's left child
        // second condition means a path from some node in u's left child to some node in u's right child

        uset.insert(s1 + u.val); // update u's set
    }
    for (int s2 : rset) {
        if (finalSum - u.val - s2 == 0) return true;
        // don't forget the path from u to some descendant in u's right child
        uset.insert(s2 + u.val); // update u's set
    }
    return false;
}

我注意到原来的问题是找到所有路径,但上面的算法是查找是否存在。我认为这个想法很相似,但这个版本使这个问题更容易解释:)

答案 8 :(得分:0)

这是O(N)解决方案

int fn(root, sum, *count)                                                                               
{                                                                                                   
    if(root == NULL)                                                                                
        return 0;                                                                                                                                                                                       
    int left =  fn(root->left, sum, count);                                                         

    int right = fn(root->left, sum, count);                                                                                                                                                            

    if(left == sum)                                                                                 
        *count++;                                                                                   

    if(right == sum)                                                                                
        *count++;                                                                                   

    if((root->data + left + right) == sum)                                                          
        *count++;                                                                                   

    return (root->data + left + right);                                                             
}

答案 9 :(得分:0)

因为我们需要具有sum == k的路径。 我假设最坏情况复杂度可以是O(total_paths_in_tree)。

那么为什么不生成每个路径并检查总和,无论如何它是一个具有负数的树,甚至不是二叉搜索树。

    struct node{
      int val;
      node *left,*right;

      node(int vl)
      {
        val = vl;
        left = NULL;
        right = NULL;
      }
   };


   vector<vector<int> > all_paths;
   vector<vector<int> > gen_paths(node* root)
   {
       if(root==NULL)
       {
          return vector<vector<int> > ();
       }

       vector<vector<int> >    left_paths = gen_paths(root->left);
       vector<vector<int> >    right_paths = gen_paths(root->right);

       left_paths.push_back(vector<int> ()); //empty path
       right_paths.push_back(vector<int> ());

       vector<vector<int> > paths_here;
       paths_here.clear();


       for(int i=0;i<left_paths.size();i++)
       {
           for(int j=0;j<right_paths.size();j++)
           {
              vector<int> vec;
              vec.clear();
              vec.insert(vec.end(), left_paths[i].begin(), left_paths[i].end());
             vec.push_back(root->val);
             vec.insert(vec.end(), right_paths[j].begin(), right_paths[j].end());
             paths_here.push_back(vec);
           }
        }

        all_paths.insert(all_paths.end(),paths_here.begin(),paths_here.end());

       vector<vector<int> > paths_to_extend;
       paths_to_extend.clear();

       for(int i=0;i<left_paths.size();i++)
       {
            paths_to_extend.push_back(left_paths[i]);
            paths_to_extend[i].push_back(root->val);
       }

       for(int i=0;i<right_paths.size();i++)
       {
           paths_to_extend.push_back(right_paths[i]);
           paths_to_extend[paths_to_extend.size()-1].push_back(root->val);
       }

       return paths_to_extend;
    }

为了生成路径,我生成了所有左路径和所有正确的路径 并将left_paths + node-&gt; val + right_paths添加到每个节点的all_paths。 并且已经发送了仍然可以扩展的路径。即从两侧+节点的所有路径。

答案 10 :(得分:0)

搜索:

Recursively traverse the tree, comparing with the input key, as in binary search tree.

If the key is found, move the target node (where the key was found) to the root position using splaysteps.

Pseudocode:


Algorithm: search (key)
Input: a search-key
1.   found = false;
2.   node = recursiveSearch (root, key)
3.   if found
4.     Move node to root-position using splaysteps;
5.     return value
6.   else
7.     return null
8.   endif
Output: value corresponding to key, if found.



Algorithm: recursiveSearch (node, key)
Input: tree node, search-key
1.   if key = node.key
2.     found = true
3.     return node
4.   endif
     // Otherwise, traverse further 
5.   if key < node.key
6.     if node.left is null
7.       return node
8.     else
9.       return recursiveSearch (node.left, key)
10.    endif
11.  else
12.    if node.right is null
13.      return node
14.    else
15.      return recursiveSearch (node.right, key)
16.    endif
17.  endif
Output: pointer to node where found; if not found, pointer to node for insertion.

答案 11 :(得分:0)

我已经改进了Arvind Upadhyay的一些答案编码逻辑。 if循环完成后,您不能使用相同的列表。所以需要创建新列表。 此外,需要保持从当前节点到搜索路径的逻辑电平的计数。如果我们找不到路径,那么在去孩子之前,我们需要从等于计数次数的递归调用中出来。

{{1}}

检查完整代码:https://github.com/ganeshzilpe/java/blob/master/Tree/BinarySearchTree.java

答案 12 :(得分:0)

// assumption node have integer value other than zero
void printAllPaths(Node root, int sum , ArrayList<Integer> path) {

   if(sum == 0) {
      print(path); // simply print the arraylist
    }

   if(root ==null) {
     //traversed one end of the tree...just return
      return;
  }
    int data = root.data;
    //this node can be at the start, end or in middle of path only if it is       //less than the sum
    if(data<=sum) {
     list.add(data);
     //go left and right
    printAllPaths(root.left, sum-data ,  path);
    printAllPaths(root.right, sum-data ,  path);

    }
   //note it is not else condition to ensure root can start from anywhere
   printAllPaths(root.left, sum ,  path);
   printAllPaths(root.right, sum ,  path);
}

答案 13 :(得分:0)

以下是使用recurssion的解决方案。 我们按顺序遍历二叉树,当我们向下移动一个级别时,我们通过将当前级别的权重加到树的先前级别的权重来总计总路径权重,如果我们达到我们的总和,那么我们打印出路。该解决方案将处理我们在任何给定路径路径上可能有多个解决方案的情况。

假设您有一个以root为根的二叉树。

#include <iostream>
#include <vector>
using namespace std;

class Node
{
private:
    Node* left;
    Node* right;
    int value;

public:
    Node(const int value)
    {
        left=NULL;
        right=NULL;
        this->value=value;
    }

    void setLeft(Node* left)
    {
        this->left=left;
    }

    void setRight(Node* right)
    {
        this->right = right;
    }

    Node* getLeft() const
    {
        return left;
    }

    Node* getRight() const
    {
        return right;
    }

    const int& getValue() const
    {
        return value;
    }
};

//get maximum height of the tree so we know how much space to allocate for our
//path vector

int getMaxHeight(Node* root)
{
    if (root == NULL)
        return 0;

    int leftHeight = getMaxHeight(root->getLeft());
    int rightHeight = getMaxHeight(root->getRight());

    return max(leftHeight, rightHeight) + 1;
}

//found our target sum, output the path
void printPaths(vector<int>& paths, int start, int end)
{
    for(int i = start; i<=end; i++)
        cerr<<paths[i]<< " ";

    cerr<<endl;
}

void generatePaths(Node* root, vector<int>& paths, int depth, const int sum)
{
    //base case, empty tree, no path
    if( root == NULL)
        return;

    paths[depth] = root->getValue();
    int total =0;

    //sum up the weights of the nodes in the path traversed
    //so far, if we hit our target, output the path
    for(int i = depth; i>=0; i--)
    {
        total += paths[i];
        if(total == sum)
            printPaths(paths, i, depth);
    }

    //go down 1 level where we will then sum up from that level
    //back up the tree to see if any sub path hits our target sum
    generatePaths(root->getLeft(), paths, depth+1, sum);
    generatePaths(root->getRight(), paths, depth+1, sum);
}

int main(void)
{
    vector<int> paths (getMaxHeight(&root));
    generatePaths(&root, paths, 0,0);
}

空间复杂度取决于树的高度,假设这是一个平衡树,则空间复杂度为0(log n),基于递归堆栈的深度。 时间复杂度O(n Log n) - 基于平衡树,其中每个级别和每个级别有n个节点将完成n个工作量(对路径求和)。我们还知道树高是由平衡二叉树的O(log n)限定的,因此在平衡二叉树上为每个级别完成的n个工作量给出了O(n log n)的运行时间

答案 14 :(得分:0)

https://codereview.stackexchange.com/questions/74957/find-all-the-paths-of-tree-that-add-to-a-input-value

我试过一个答案,期待代码审查。我的代码以及审阅者应该是有用的来源。

答案 15 :(得分:0)

# include<stdio.h>
# include <stdlib.h>
struct Node
{
    int data;
    struct Node *left, *right;
};

struct Node * newNode(int item)
{
    struct Node *temp =  (struct Node *)malloc(sizeof(struct Node));
    temp->data = item;
    temp->left =  NULL;
    temp->right = NULL;
    return temp;
}
void print(int p[], int level, int t){
    int i;
    for(i=t;i<=level;i++){
        printf("\n%d",p[i]);
    }
}
void check_paths_with_given_sum(struct Node * root, int da, int path[100], int level){

     if(root == NULL)
        return ;
    path[level]=root->data;
    int i;int temp=0;
    for(i=level;i>=0;i--){
        temp=temp+path[i];
        if(temp==da){
            print(path,level,i);
        }
    }
        check_paths_with_given_sum(root->left, da, path,level+1);
        check_paths_with_given_sum(root->right, da, path,level+1);

}
int main(){
    int par[100];
 struct Node *root = newNode(10);
    root->left = newNode(2);
    root->right = newNode(4);
    root->left->left = newNode(1);
    root->right->right = newNode(5);
    check_paths_with_given_sum(root, 9, par,0);


}

这有效.....

答案 16 :(得分:0)

void printpath(int sum,int arr[],int level,struct node * root)
{
  int tmp=sum,i;
  if(root == NULL)
  return;
  arr[level]=root->data;
  for(i=level;i>=0;i--)
  tmp-=arr[i];
  if(tmp == 0)
  print(arr,level,i+1);
  printpath(sum,arr,level+1,root->left);
  printpath(sum,arr,level+1,root->right);
}
 void print(int arr[],int end,int start)
{  

int i;
for(i=start;i<=end;i++)
printf("%d ",arr[i]);
printf("\n");
}

复杂性(n logn) 空间复杂度(n)

答案 17 :(得分:0)

public void printPath(N n) {
    printPath(n,n.parent);
}

private void printPath(N n, N endN) {
    if (n == null)
        return;
    if (n.left == null && n.right == null) {
        do {
            System.out.print(n.value);
            System.out.print(" ");
        } while ((n = n.parent)!=endN);
        System.out.println("");
        return;
    }
    printPath(n.left, endN);
    printPath(n.right, endN);
}

您可以在n节点上打印树路径。喜欢这个printPath(n);