【问题标题】:List the elements that appeared in both list?列出两个列表中出现的元素?
【发布时间】:2017-06-23 00:51:08
【问题描述】:

给定两个排序数组(当然没有重复),有没有办法找出并打印出现在两个数组中的所有元素?

我知道是否可以通过迭代一个数组并构建一个哈希表,然后迭代另一个数组并查找构建的表。但这需要 O(n) 的空间。

我正在尝试查看是否存在需要恒定额外空间的方法,并且只需要对每个数组进行不超过一次的迭代。有可能吗?

现在如果上述问题是可能的,如果两个排序列表存储在两个二叉搜索树中,考虑到共谋限制,相同的方法是否仍然适用?

【问题讨论】:

    标签: arrays algorithm sorting binary-search-tree


    【解决方案1】:

    对于数组,执行相当于合并操作,但没有输出。在合并过程中,将检测到任何重复项。这将只涉及每个列表一次通过,并且将在到达任一列表的末尾时立即终止。

    二叉搜索树可以使用堆栈迭代遍历,但最坏情况的堆栈空间是 O(n)。 Morris 遍历(进行网络搜索)可以在不使用堆栈的情况下遍历二叉树,并且通过更改和恢复树中的链接具有 O(n) 时间复杂度(每个节点将被访问多次,但多次的时间开销n 的时间复杂度仍然是 O(n))。典型的 Morris 遍历函数对整棵树进行操作。这将需要更改,以便它按顺序与每个节点一起返回,以便可以使用类似合并的逻辑来检查重复项。我为此编写了一些测试代码,以便在您遇到困难时提供帮助。

    按顺序遍历二叉树时,每个当前节点都有一个前驱节点,该节点按顺序出现在当前节点之前。前驱节点将有一个空的“右”指针。在 Morris 遍历期间,每个前驱节点的“右”指针从 null 更改为指向它的后继节点。最终到达前驱节点时,跟随其右指针到达其后继节点,然后将后继节点的前驱节点的右指针恢复为空。

    因为已经 2 天了,这里是一个 Morris 遍历函数的示例代码,它返回一个指向每个节点的指针。部分逻辑在 main() 的 for 循环中,该循环在再次调用遍历函数之前将返回的指针向右移动(另一种方法是让辅助函数向右移动,然后调用主遍历函数) :

    #include<stdio.h>
    #include<stdlib.h>
    
    /* binary tree node */
    typedef struct BTNODE_
    {
        struct BTNODE_* left;
        struct BTNODE_* right;
        int data;
    }BTNODE;
    
    /* traverse binary tree without stack */
    /* initial input parameter is pointer to root */
    /* predecessor of cur is largest value < cur */
    /* predecessor of cur = cur->left->right->right->right ... */
    BTNODE * MorrisTraversal(BTNODE *cur)
    {
    BTNODE *pre;
        if(cur == NULL)
            return(NULL);
        while(cur != NULL){
            /* if left end of branch, return */
            if(cur->left == NULL)
                return(cur);
            /* advance pre to predecessor of cur */
            pre = cur->left;
            while(pre->right != NULL && pre->right != cur)
                pre = pre->right;
            /* if right end of branch, change pre->right = cur */
            /*  and advance cur left */
            if(pre->right == NULL){
                pre->right = cur;
                cur = cur->left;
            /* else back at cur, so restore pre->right = NULL */
            /*  and return */
            } else {
                pre->right = NULL;
                return(cur);
            }
        }
        return(NULL);
    }
    
    BTNODE* newbtNode(int data)
    {
    BTNODE* btNode = (BTNODE*) malloc(sizeof(BTNODE));
        btNode->left = NULL;
        btNode->right = NULL;
        btNode->data = data;
        return(btNode);
    }
    
    int main()
    {
    BTNODE *cur;
    /* create a 15 element binary tree */
    BTNODE *root = newbtNode(8);
        root->left                = newbtNode( 4);
        root->left->left          = newbtNode( 2);
        root->left->left->left    = newbtNode( 1);
        root->left->left->right   = newbtNode( 3);
        root->left->right         = newbtNode( 6);
        root->left->right->left   = newbtNode( 5);
        root->left->right->right  = newbtNode( 7);
        root->right               = newbtNode(12);
        root->right->left         = newbtNode(10);
        root->right->left->left   = newbtNode( 9);
        root->right->left->right  = newbtNode(11);
        root->right->right        = newbtNode(14);
        root->right->right->left  = newbtNode(13);
        root->right->right->right = newbtNode(15);
        for(cur = root; cur; cur = cur->right){
            cur = MorrisTraversal(cur);
            printf("%2d ", cur->data);
        }
        return 0;
    }
    

    【讨论】:

    • 是的,这适用于数组。 BST 案子呢?
    • @fluter - 我更新了我的答案以包括 BST 案例。
    【解决方案2】:

    从两个数组的开头开始。

    arr[0],arr1[0]

    如果 arr[0] > arr1[0],二分查找 arr1 中等于或大于 arr1 的位置。然后你有一个类似类型 arr[1..n], arr[k..m] 的子问题,其中 k 是在二进制搜索阶段找到的索引。

    【讨论】:

    • 在这种情况下,二分搜索实际上比线性搜索效率低,因为线性搜索是摊销的常数时间,而二分搜索仍然是对数时间。
    • 我知道.. 另一种我觉得很有趣的解决方法。
    【解决方案3】:

    即使有 3 个循环,总操作数也会随着输入项的数量线性增长。这是 O(n)。虽然第一个 while 不像其他两个那样以可预测的方式递增,但它不会改变 O(n)。这基本上是一个合并操作。在合并过程中,将检测到任何重复项。这将只涉及每个列表一次通过,并且将在到达任一列表的末尾时立即终止。

    function mergetwosorted(a, b) {
    
      var c = new Array(a.length + b.length),
        ai = 0,
        bi = 0,
        ci = 0;
    
    
      while (ai < a.length && bi < b.length) {
    
        if (a[ai] < b[bi]) {
          c[ci] = a[ai];
          ai++;
        } else {
          c[ci] = b[bi]
          bi++;
        }
        ci++;
      }
    
      while (ai < a.length) {
        c[ci] = a[ai];
        ai++
        ci++
      }
      while (bi < b.length) {
        c[ci] = b[bi];
        ci++;
        bi++;
      }
      return c;
    
    }
    
    
    console.log(mergetwosorted([1, 2, 3, 4], [2, 3, 4, 5, 6]))

    【讨论】:

      【解决方案4】:

      我想你可以看看 std::set_difference http://en.cppreference.com/w/cpp/algorithm/set_intersection

      我尝试使用通常使用二叉搜索树实现的 set 并且它可以工作。

      set<int> s1 = {1, 2, 4, 5};
      set<int> s2 = {0, 2, 4, 7};
      vector<int> v;
      set_intersection(s1.begin(), s1.end(),
                       s2.begin(), s2.end(),
                       back_inserter(v));
      for (auto i : v)
          cout << i << '\n';
      

      输出

      2
      4
      

      这是问题的递归思路 对于任何长度为 n 的数组 A,我将使用 A[2 .. n] 来表示数组 [A[2], ..., A[n]]

      令 A, B 为长度为 n, m 的两个输入数组。 如果其中一个为空,则答案是微不足道的。

      否则,让我们比较第一个条目。

      如果 A[0] == B[0],那么我们知道 A[0] 出现在两个数组中,我们将 A[0] 存储到输出列表并继续搜索数组 A[2 .. n] 和B[2 .. 米]

      如果 A[0]

      类似地,如果 A[0] > B[0],我们继续搜索数组 A 和 B[2 .. n]

      然后通过递归,您可以找到答案。为什么它适用于二叉搜索树?这是因为我们只需要每个输入的递增序列。我们可以通过在树上按顺序遍历得到这样的序列。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-11-20
        • 1970-01-01
        • 2018-11-11
        • 1970-01-01
        • 2018-10-08
        • 1970-01-01
        • 2019-09-02
        • 1970-01-01
        相关资源
        最近更新 更多