在BST中查找总计为提供值的元素

时间:2013-04-23 23:54:16

标签: java algorithm data-structures big-o binary-search-tree

我正试图找到解决问题的方法

Find two elements in balanced BST which sums to a given a value.

约束时间O(n)和空间O(logn)。

我想知道以下算法是否有效。

int[] findSum(Node root, int sum){
   int[] sumArray;
   for (int i=0;i<sum;i++) {
      if (root.contains(i) && root.contains(sum-i)) {
         sumArray[0] = i;
         sumArray[1] = sum-i;
      }
   }
}

我明白我的方法可能是错的。我很感激对我的伪代码/更好算法的任何反馈/更正

5 个答案:

答案 0 :(得分:8)

我相信你所拥有的方法会奏效,但没有适当的时间限制。

让我们首先分析这种算法的复杂性。请注意,此处需要考虑两个不同的参数。首先,BST中的元素总数。如果使BST变大或变小,则算法完成所需的时间将更长或更短。我们称这个数字为n。其次,您希望值总和的总数。让我们称之为U。

因此,让我们看看您当前的算法是做什么的。现在,它迭代循环O(U)次,在每次迭代时检查BST中是否存在两个值。每次BST查找都需要时间O(log n),因此算法所做的总工作量为O(U log n)。但是,您的算法仅使用常量空间(即空间O(1))。

不幸的是,这个运行时并不是很好。如果目标数量非常大(例如,1,000,000,000),那么你的算法将需要很长时间才能完成,因为U会非常大。

现在的问题是如何更有效地解决这个问题。幸运的是,我们可以使用一种非常可爱的算法来解决问题,它将利用BST元素按排序顺序存储的事实。

让我们首先解决一个类似的问题,这个问题与你提出的问题略有不同。假设我没有给你一个BST和一个目标号码,而是给你一个排序的数组和一个目标号码,然后问同一个问题:这个排序数组中有两个数字总和到目标?例如,我可能会给你这个数组:

 0  1  3  6  8  9  10  14  19

假设你想知道这个数组中的两个数字总和是16。你怎么能这样做?

您可以尝试与之前相同的方法:检查数组是否包含0和16,1和15,2和14等,直到找到一对或用完要检查的值。检查排序数组中是否存在元素需要使用二进制搜索时间O(log n),因此该算法仍需要O(U log n)时间。 (如果你知道值很好地分配了,那么你可以想象使用interpolation search加快速度,这会给O(U log log n)运行时期望,但是那个大U项仍然是个问题。 p>

因此,让我们考虑一种不同的方法。从根本上说,你所做的事情要求你明确列举总结为U的所有对。然而,他们中的大多数都不会在那里,事实上,大部分时间既不该对中的元素将存在。通过使用以下算法,我们可以更快地完成任务:

  • 对于x数组的每个元素,检查U-x是否在数组中。
  • 如果是,请报告成功。
  • 否则,如果不存在此类对,则报告失败。

此算法将要求您查看数组中的总共O(n)个元素,每次执行O(log n)工作以查找匹配对。在这种最坏的情况下,这将花费O(n log n)时间并使用O(1)空间。如果U是一个庞大的数字,这比以前要好得多,因为它根本不再依赖于U!

然而,通过巧妙地观察问题的结构,我们可以进一步加快速度。让我们假设我们正在查看数组中的数字x并进行二进制搜索以查找U - x。如果我们找到它,我们就完成了。如果没有,我们会发现数组中的第一个数字大于U - x。我们称之为数字z。

所以现在假设我们想要看看数y是否可以是总和为U的那一对的一部分,而且假设y大于x。在那种情况下,我们知道

  

y + z

     

&GT; x + z

     

&GT; x +(U - x)

     

= U

这说明y和z的总和大于,因此它可能不是U.这是什么意思?好吧,我们通过尝试对与x配对的元素进行二元搜索来找到z,总结为U.我们刚刚展示的是,如果您尝试将z添加到任何数组中的数字大于x,总数必须超过U.换句话说,z不能与大于x的任何东西配对。同样,任何大于z的东西都不能与大于x的东西配对,因为它必须总结为大于U的东西。

基于这种观察,我们可以尝试使用不同的算法。让我们像以前一样拿出我们的数组,看看我们是否能找到一个总和为16的对:

 0  1  3  6  8  9  10  14  19

让我们保持两个指针 - 一个&#34;左手边&#34;指针lhs和&#34;右边&#34;指针rhs:

 0  1  3  6  8  9  10  14  19
 ^                          ^
lhs                        rhs

当我们总结这两个数字时,我们得到19,超过U.现在,我们加起来的任何数字对都必须使其较低的数字至少与lhs数字一样大,即0。因此,如果我们在这里总结了使用数字19的任何对,我们就知道总和太大了。因此,我们可以不考虑任何包含19的对。这就离开

 0  1  3  6  8  9  10  14  19
 ^                      ^
lhs                    rhs

现在,看看这个总和(14),它太小了。使用与以前类似的逻辑,我们可以有把握地说,使用0的剩余数组中的任何总和必须最终给出小于16的总和,因为总和中的第二个数字最多为14.因此,我们可以排除0:

 0  1  3  6  8  9  10  14  19
    ^                   ^
   lhs                 rhs

我们开始看到一种模式:

  • 如果总和太小,请提前lhs。
  • 如果总和太大,则递减rhs。

最终,我们会找到一对总和达到16的对,或者lhs和rhs将相互撞击,此时我们保证没有对总和达到16。

通过此算法进行追踪,我们得到了

 0  1  3  6  8  9  10  14  19
    ^                   ^
   lhs                 rhs    (sum is 15, too small)

 0  1  3  6  8  9  10  14  19
       ^                ^
      lhs              rhs    (sum is 17, too big)

 0  1  3  6  8  9  10  14  19
       ^            ^
      lhs          rhs        (sum is 13, too small)

 0  1  3  6  8  9  10  14  19
          ^         ^
         lhs       rhs        (sum is 16, we're done!)

瞧瞧!我们得到了答案。

这有多快?好吧,在每次迭代时,lhs下降或rhs上升,算法在相遇时停止。这意味着我们进行O(n)次迭代。每次迭代最多都是常量工作,因此每次迭代最多需要O(1)次工作。这给出了总时间复杂度O(n)。

空间怎么样?好吧,我们需要存储两个指针,每个指针占用O(1)空间,因此总空间使用量为O(1)。太好了!

但这与你的问题有什么关系?连接是这样的:在每个时间点,该算法只需要记住数组中的两个数字。然后它需要能够从一个元素前进到下一个元素或从一个元素前进到前一个元素。这就是它必须做的一切。

因此,假设您使用二叉搜索树而不是使用排序数组。从两个指针开始,一个指向最小节点,一个指向最大节点。完成此设置后,您可以模拟上述算法,替换&#34;增加lhs&#34;并且&#34;减少rhs&#34;与&#34; 移动到lhs的继承者&#34;并且&#34; 转移到rhs的前身。&#34;可以实现这些操作,以便它们使用总共O(log n)空间(假设树是平衡的)并且使得每种类型的n个操作的任何序列总共花费O(n)时间(无论是否树是平衡的)。因此,如果您使用上面修改的算法来处理BST,您将获得一个花费时间O(n)并仅使用O(log n)空间的算法!

实施细节有点棘手,我将把这部分作为练习留给你。如果您不熟悉继任者和前任,那么网上有很多好的资源。它们是BST上的标准算法,对于了解它们非常有用。我还会将阵列算法正确性的形式证明作为练习,尽管它并不太难。

希望这有帮助!

答案 1 :(得分:0)

我认为你必须两次搜索树。但首先,你正在接受一个名为sum的整数但是它突然变成一个数组?那是一个错字吗?我假设你打算接受一个数组。

你必须遍历树和每个节点,从根调用另一个遍历,寻找一个可以添加到第一个节点元素的节点,该节点等于总和。

此外,您不能将sum作为变量和数组在同一方法中。

现在我刚看到你的编辑,以数字17为例。首先搜索0,如果找到,则调用另一个搜索从根开始搜索17 -0。如果找不到,请将0增加到1并搜索17-1,直到找到两个给你17的数字。

修改

//we're looking for two numbers that equal 17 when added
Node runner;
Node root;
int i;
int [] sum_total;

void findSum(int sum){
    int search_1st = 0;
    sum_total = new int[2];
    search(int search_1st);
}   

search( Node root, int num1){
    if(i == 3){
        return;
    }
    Node runner = root;
    if(ruunner == null){
    return ;
    }
    if(runner.element == num1){
        sum_total[i] = num1;
        i++;
        if(i == 3){
            return;
        }
        //now search for sum - num1 with root
        search(root, sum - num1);
    }else{
        if(runner.left < num1){
            search(runner.right, num1);
        }else{
            search(runner.left, num1);
        }
    }
}

答案 2 :(得分:0)

来自http://www.geeksforgeeks.org/find-a-pair-with-given-sum-in-bst/

/* In a balanced binary search tree isPairPresent two element which sums to
   a given value time O(n) space O(logn) */
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 100

// A BST node
struct node
{
    int val;
    struct node *left, *right;
};

// Stack type
struct Stack
{
    int size;
    int top;
    struct node* *array;
};

// A utility function to create a stack of given size
struct Stack* createStack(int size)
{
    struct Stack* stack =
        (struct Stack*) malloc(sizeof(struct Stack));
    stack->size = size;
    stack->top = -1;
    stack->array =
        (struct node**) malloc(stack->size * sizeof(struct node*));
    return stack;
}

// BASIC OPERATIONS OF STACK
int isFull(struct Stack* stack)
{   return stack->top - 1 == stack->size;  }

int isEmpty(struct Stack* stack)
{   return stack->top == -1;   }

void push(struct Stack* stack, struct node* node)
{
    if (isFull(stack))
        return;
    stack->array[++stack->top] = node;
}

struct node* pop(struct Stack* stack)
{
    if (isEmpty(stack))
        return NULL;
    return stack->array[stack->top--];
}

// Returns true if a pair with target sum exists in BST, otherwise false
bool isPairPresent(struct node *root, int target)
{
    // Create two stacks. s1 is used for normal inorder traversal
    // and s2 is used for reverse inorder traversal
    struct Stack* s1 = createStack(MAX_SIZE);
    struct Stack* s2 = createStack(MAX_SIZE);

    // Note the sizes of stacks is MAX_SIZE, we can find the tree size and
    // fix stack size as O(Logn) for balanced trees like AVL and Red Black
    // tree. We have used MAX_SIZE to keep the code simple

    // done1, val1 and curr1 are used for normal inorder traversal using s1
    // done2, val2 and curr2 are used for reverse inorder traversal using s2
    bool done1 = false, done2 = false;
    int val1 = 0, val2 = 0;
    struct node *curr1 = root, *curr2 = root;

    // The loop will break when we either find a pair or one of the two
    // traversals is complete
    while (1)
    {
        // Find next node in normal Inorder traversal. See following post
        // http://www.geeksforgeeks.org/inorder-tree-traversal-without-recursion/
        while (done1 == false)
        {
            if (curr1 != NULL)
            {
                push(s1, curr1);
                curr1 = curr1->left;
            }
            else
            {
                if (isEmpty(s1))
                    done1 = 1;
                else
                {
                    curr1 = pop(s1);
                    val1 = curr1->val;
                    curr1 = curr1->right;
                    done1 = 1;
                }
            }
        }

        // Find next node in REVERSE Inorder traversal. The only
        // difference between above and below loop is, in below loop
        // right subtree is traversed before left subtree
        while (done2 == false)
        {
            if (curr2 != NULL)
            {
                push(s2, curr2);
                curr2 = curr2->right;
            }
            else
            {
                if (isEmpty(s2))
                    done2 = 1;
                else
                {
                    curr2 = pop(s2);
                    val2 = curr2->val;
                    curr2 = curr2->left;
                    done2 = 1;
                }
            }
        }

        // If we find a pair, then print the pair and return. The first
        // condition makes sure that two same values are not added
        if ((val1 != val2) && (val1 + val2) == target)
        {
            printf("\n Pair Found: %d + %d = %d\n", val1, val2, target);
            return true;
        }

        // If sum of current values is smaller, then move to next node in
        // normal inorder traversal
        else if ((val1 + val2) < target)
            done1 = false;

        // If sum of current values is greater, then move to next node in
        // reverse inorder traversal
        else if ((val1 + val2) > target)
            done2 = false;

        // If any of the inorder traversals is over, then there is no pair
        // so return false
        if (val1 >= val2)
            return false;
    }
}

// A utility function to create BST node
struct node * NewNode(int val)
{
    struct node *tmp = (struct node *)malloc(sizeof(struct node));
    tmp->val = val;
    tmp->right = tmp->left =NULL;
    return tmp;
}

// Driver program to test above functions
int main()
{
    /*
                   15
                /     \
              10      20
             / \     /  \
            8  12   16  25    */
    struct node *root =  NewNode(15);
    root->left = NewNode(10);
    root->right = NewNode(20);
    root->left->left = NewNode(8);
    root->left->right = NewNode(12);
    root->right->left = NewNode(16);
    root->right->right = NewNode(25);

    int target = 28;
    if (isPairPresent(root, target) == false)
        printf("\n No such values are found\n");

    getchar();
    return 0;
}

答案 3 :(得分:0)

或者,遍历树并将所有值存储在HashSet中。然后进行另一次遍历,看看是否(target - nodeValue)在集合中。它可以在O(n)时间,O(n)空间中完成。

答案 4 :(得分:0)

这个想法与之前的解决方案相同,只是我正在使用两个堆栈 - 一个跟随inorder(stack1),另一个跟随逆序 - inorder order(stack2)。一旦我们到达BST中最左侧和最右侧的节点,我们就可以开始将它们一起比较。

如果总和小于所需值,则从stack1弹出,否则从stack2弹出。以下是java实现相同:

public int sum2(TreeNode A, int B) {
    Stack<TreeNode> stack1 = new Stack<>();
    Stack<TreeNode> stack2 = new Stack<>();
    TreeNode cur1 = A;
    TreeNode cur2 = A;

    while (!stack1.isEmpty() || !stack2.isEmpty() ||
            cur1 != null || cur2 != null) {
        if (cur1 != null || cur2 != null) {
            if (cur1 != null) {
                stack1.push(cur1);
                cur1 = cur1.left;
            }

            if (cur2 != null) {
                stack2.push(cur2);
                cur2 = cur2.right;
            }
        } else {
            int val1 = stack1.peek().val;
            int val2 = stack2.peek().val;

            // need to break out of here
            if (stack1.peek() == stack2.peek()) break;

            if (val1 +  val2 == B) return 1;

            if (val1 + val2 < B) {
                cur1 = stack1.pop();
                cur1 = cur1.right;
            } else {
                cur2 = stack2.pop();
                cur2 = cur2.left;
            }
        }
    }

    return 0;
}