数组中的两个元素,其xor是最大的

时间:2012-02-16 22:30:47

标签: arrays algorithm bit-manipulation xor

给定一个整数数组,你必须找到两个XOR最大的元素。

有天真的方法 - 只需挑选每个元素并使用其他元素进行xoring,然后比较结果以找到该对。

除此之外,有没有有效的算法?

6 个答案:

答案 0 :(得分:40)

我认为我有一个O(n lg U)算法,其中U是最大的数字。这个想法类似于user949300,但有更多细节。

直觉如下。当您将两个数字一起进行异或运算时,要获得最大值,您希望在最高位置获得1,然后在此位置具有1的配对,您希望在下一个位置配对1可能的最高职位等。

所以算法如下。首先在数字中的任何地方找到最高的1位(你可以在时间O(n lg U)中通过对每个n个数字执行O(lg U)工作来执行此操作)。现在,将数组拆分为两部分 - 其中一个数字在该位中为1,而在该位中为0。任何最佳解决方案都必须将第一个点中的1和数字与该点中的0组合,因为这样会使1位尽可能高。任何其他配对都有0。

现在,递归地,我们想要找到1和0组中具有最高1的数字的配对。为此,在这两组中,将它们分成四组:

  • 以11开头的数字
  • 以10开头的数字
  • 以01开头的数字
  • 以00开头的数字

如果11组和00组或10组和01组中有任何数字,则它们的XOR是理想的(从11开始)。因此,如果这些组中的任何一组不为空,则递归地从这些组计算理想解,然后返回那些子问题解的最大值。否则,如果两个组都为空,则表示所有数字在其第二个位置必须具有相同的数字。因此,以1开头的数字和从0开始的数字的最佳XOR将最终使下一个第二位取消,所以我们应该看第三位。

这给出了以下递归算法,从两个由MSB划分的数字组开始,给出了答案:

  • 给定组1和组0以及位索引i:
    • 如果位索引等于位数,则返回1组中(唯一)编号的XOR和0组中的(唯一)编号。
    • 从这些组中构建组11,10,01和00。
    • 如果第11组和第00组是非空的,则递归地找到从第i + 1位开始的这两组的最大XOR。
    • 如果组10和组01都是非空的,则递归地找到这两组的最大异或,从第i + 1位开始。
    • 如果可以使用上述配对中的任何一个,则返回递归找到的最大配对。
    • 否则,所有数字在位置i必须具有相同的位,因此通过查看第1组和第0组上的第i + 1位返回找到的最大对。

要启动算法,您实际上只需将初始组中的数字划分为两组 - 具有MSB 1的数字和具有MSB 0的数字。然后,您可以使用两组来激活对上述算法的递归调用。号。

例如,考虑数字5 1 4 3 0 2.这些表示

101  001  100   011   000   010

我们首先将它们分成1组和0组:

101  100
001  011  000  010

现在,我们应用上述算法。我们将其分为11,10,11和00组:

11:
10:  101  100
01:  011  010
00:  000  001

现在,我们不能将任何11个元素与00元素配对,所以我们只对10和01组进行递归。这意味着我们构建了100,101,010和011组:

101: 101
100: 100
011: 011
010: 010

现在我们只关注其中只有一个元素的桶,我们可以检查对101和010(给出111)和100和011(给出111)。这两个选项都适用于此,因此我们得出最佳答案为7。

让我们考虑一下这个算法的运行时间。请注意,最大递归深度为O(lg U),因为数字中只有O(log U)位。在树中的每个级别,每个数字恰好出现在一个递归调用中,并且每个递归调用确实与0和1组中的数字总数成比例,因为我们需要按它们的位分配它们。因此,递归树中有O(log U)级别,并且每个级别执行O(n)工作,总工作为O(n log U)。

希望这有帮助!这是一个很棒的问题!

答案 1 :(得分:4)

忽略符号位,其中一个值必须是具有最高有效位设置的值之一。 除非所有值都设置了该位,否则在这种情况下,您将转到未在所有值中设置的下一个最高有效位。因此,您可以通过查看HSB来削减第一个值的可能性。例如,如果可能性

0x100000
0x100ABC
0x001ABC
0x000ABC

最大对的第1个值必须是0x100000或0x10ABCD。

@内部服务器错误我认为最小的必然是正确的。我没有一个很好的想法削减第二个价值。在可能的第一个值列表中,不是的任何值。在我的例子中,0x001ABC或0x000ABC。

答案 2 :(得分:4)

这可以使用TrieO(NlogN)时间复杂度中解决。

  • 构建一个特里。对于每个整数键,trie的每个节点将从最高位开始保存每个位(0或1)。
  • 现在为arr[i]的每个arr[0, 1, ..... N]元素
    • 执行查询以检索arr[i]可能的最大xor值。我们知道不同类型的位(0 ^ 11 ^ 0)的xor总是1。因此在查询每个位时,尝试遍历持有相反位的节点。这将使特定位1导致最大化xor值。如果没有相反位的节点,则只遍历相同的位节点。
    • 查询后,将arr[i]插入trie。
    • 对于每个元素,请跟踪可能的最大Xor值。
    • 在遍历每个节点的过程中,构建Xor最大化的另一个键。

对于N元素,我们需要为每个元素提供一个查询(O(logN))和一个插入(O(logN))。因此总体时间复杂度为O(NlogN)

你可以找到关于它是如何工作的很好的图解说明in this thread

以下是上述算法的C ++实现:

const static int SIZE = 2;
const static int MSB = 30;
class trie {
private:
    struct trieNode {
        trieNode* children[SIZE];
        trieNode() {
            for(int i = 0; i < SIZE; ++i) {
                children[i] = nullptr;
            }
        }
        ~trieNode() {
            for(int i = 0; i < SIZE; ++i) {
                delete children[i];
                children[i] = nullptr;
            }
        }
    };
    trieNode* root;
public:
    trie(): root(new trieNode()) {
    }
    ~trie() {
        delete root;
        root = nullptr;
    }

    void insert(int key) {
        trieNode* pCrawl = root;
        for(int i = MSB; i >= 0; --i) {
            bool bit = (bool)(key & (1 << i));
            if(!pCrawl->children[bit]) {
                pCrawl->children[bit] = new trieNode();
            }
            pCrawl = pCrawl->children[bit];
        }
    }

    int query(int key, int& otherKey) {
        int Xor = 0;
        trieNode *pCrawl = root;
        for(int i = MSB; i >= 0; --i) {
            bool bit = (bool)(key & (1 << i));
            if(pCrawl->children[!bit]) {
                pCrawl = pCrawl->children[!bit];
                Xor |= (1 << i);
                if(!bit) {
                    otherKey |= (1 << i); 
                } else {
                    otherKey &= ~(1 << i);
                }
            } else {
                if(bit) {
                    otherKey |= (1 << i); 
                } else {
                    otherKey &= ~(1 << i);
                }
                pCrawl = pCrawl->children[bit];
            }
        }
        return Xor;
    }
};

pair<int, int> findMaximumXorElements(vector<int>& arr) {
    int n = arr.size();
    int maxXor = 0;
    pair<int, int> result; 
    if(n < 2) return result;
    trie* Trie = new trie();
    Trie->insert(0); // insert 0 initially otherwise first query won't find node to traverse
    for(int i = 0; i < n; i++) {
        int elem = 0;
        int curr = Trie->query(arr[i], elem);
        if(curr > maxXor) {
            maxXor = curr;
            result = {arr[i], elem};
        }
        Trie->insert(arr[i]);
    }
    delete Trie;
    return result;
}

答案 3 :(得分:3)

一个非常有趣的问题! 这是我的想法:

  • 首先使用二进制文件从所有数字构建二叉树 表示并首先将它们排序到树中最重要的位置 (添加前导零以匹配最长的数字)。完成每条路径 从根到任何叶子代表原始的一个数字 集。
  • 让a和b成为树节点的指针并在根处初始化它们。
  • 现在沿着树移动a和b,尝试在每一步使用相反的边缘,即如果向下移动0边缘,b向下移动1边缘,除非它不可能。

如果a和b到达叶子,则应指向两个数字,其中“极少”相同的位。

我刚刚制作了这个算法并且不知道它是否正确或如何证明它。但是它应该在O(n)运行时间内。

答案 4 :(得分:1)

创建一个递归函数,它将两个整数列表A和B作为参数。作为返回值,它返回两个整数,一个来自A,一个来自B,这最大化了两者的XOR。如果所有整数都为0,则返回(0,0)。否则,该函数执行一些处理并递归调用两次,但整数较小。在其中一个递归调用中,它考虑从列表A中取一个整数来提供1到位k,而在另一个调用中,它考虑从列表B中取一个整数来提供1到位k。

我现在没有时间填写细节,但也许这足以看到答案了?另外,我不确定运行时间是否会比N ^ 2好,但可能会是。

答案 5 :(得分:-2)

我们可以在O(n)时间内找到最大数量,然后循环遍历每个元素执行xor的数组。假设xor运算成本为O(1),我们可以在O(n)时间内找到两个数的max xor。