【问题标题】:Two elements in array whose xor is maximum数组中异或最大的两个元素
【发布时间】:2012-03-08 08:56:04
【问题描述】:

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

有一种简单的方法——只需选择每个元素并与其他元素进行异或运算,然后比较结果以找到对。

除此之外,还有什么高效的算法吗?

【问题讨论】:

  • 一个好的赌注是取最大和最小值,因为小值的位不太可能在异或过程中“破坏”高值的“好”高位。
  • 因为 arr={8,4,2} 失败答案是 8^4 并且 4 不是最小的...
  • @500-InternalServerError: 这肯定是一对数字,其中一个设置了最高位,另一个设置了它。
  • 是否允许重复元素或所有整数都是唯一的?

标签: arrays algorithm bit-manipulation xor


【解决方案1】:

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

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

所以算法如下。首先在数字中的任意位置找到最高的1 位(您可以通过对每个n 数字执行O(lg U) 来及时做到这一点O(n lg U))。现在,将数组分成两部分 - 一个在该位中具有 1 的数字和在该位中具有 0 的组。任何最佳解决方案都必须将第一个位置的带有1 的数字与该位置的带有0 的数字组合起来,因为这会使1 位尽可能高。任何其他配对都有一个0

现在,我们希望递归地从10 组中找到具有最高1 的数字对。为此,将这两组分为四组:

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

如果1100 组或1001 组中有任何数字,它们的异或将是理想的(以11 开头)。因此,如果这些组对中的任何一个不为空,则从这些组递归计算理想解决方案,然后返回这些子问题解决方案的最大值。否则,如果两个组都是空的,这意味着所有数字的第二个位置必须具有相同的数字。因此,以1 开头的数字和以0 开头的数字的最佳异或最终会抵消下一个第二位,所以我们应该只看第三位。

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

  • 给定组1 和组0 以及位索引i
    • 如果位索引等于位数,则返回 1 组中的(唯一)数与 0 组中的(唯一)数的异或。
    • 从这些组中构造组 11100100
    • 如果组11 和组00 不为空,则递归查找从位i + 1 开始的这两个组的最大异或。
    • 如果组10 和组01 不为空,则递归查找这两个组的最大异或,从位i + 1 开始。
    • 如果上述任一配对是可能的,则返回递归找到的最大配对。
    • 否则,所有数字在i 位置必须具有相同的位,因此返回通过查看组10 上的位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

现在,我们应用上述算法。我们将其分为11100100 组:

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

现在,我们无法将任何11 元素与00 元素配对,因此我们只对1001 组进行递归。这意味着我们构建了100101010011 组:

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

现在我们只剩下一个元素的桶了,我们可以检查101010(给出111)和100011(给出@ 987654396@)。这两种方法都适用,因此我们得到最佳答案是7

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

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

【讨论】:

  • 我还考虑过查看不止一个位(11 对 10),我的直觉是,到那时,只爆破所有组合可能会更快。而且我不想那么努力地修复所有可能会产生的错误 :-) 显然取决于 N 有多大,对于巨大的 N,您的方法会更快。
  • 数字中有 O(log U) 位是什么意思?根据定义,有一个带有 U 位的数字,所以是 O(U)。我认为你的运行时间至少是 O(NU)。
  • @DavidGrayson-假设存在的最大数字是数字 U(如宇宙上限 {1, 2, 3, ..., U}),那么每个数字都有O(log U) 位。这是为了指出,比特数与最大数的大小成对数。所以是的,如果有 U 位,那么运行时间是 O(NU)。我只是使用 U 作为最大数而不是位数。
  • 您假设最高相关位位置为 0 的数字集是非空的。另外,我不相信您可以在 O(log(U)) 时间内确定 U 位数中设置的最高位。
  • 此算法在以下情况下失败: [4,6,7] ; [8,10,2],[14,15,9,3,2] 和 [15,15,9,3,2]。查看此分析和此算法的固定版本:discuss.leetcode.com/topic/64431/…
【解决方案2】:

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

  • 构造一个树。对于每个整数键,trie 的每个节点都将保存从最高有效位开始的每个位(0 或 1)。
  • 现在对于arr[0, 1, ..... N] 的每个arr[i] 元素
    • 执行查询以检索arr[i] 可能的最大异或值。我们知道不同类型位的异或(0 ^ 11 ^ 0)总是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;
}

【讨论】:

  • 惊人的解决方案!我发现你的答案在这个线程中是最容易理解的。我有一个问题,提前对 necro-bumping 表示抱歉,但是:logarithmic 部分来自哪里?我的意思是,对于数组中的每个元素,我们执行插入和查询,每个都精确地执行C 步骤(整数编码的宽度,在我们的示例中为32),所以我们的复杂性不应该像N * C插入步骤加上N * C 查询步骤,结果为O(2 * (N * C)) &lt;=&gt; O(N)?
【解决方案3】:

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

0x100000
0x100ABC
0x001ABC
0x000ABC

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

@internal 服务器错误我认为最小的不一定是正确的。我没有减少第二个值的好主意。可能的第一个值列表中不是的任何值。在我的示例中,0x001ABC 或 0x000ABC。

【讨论】:

    【解决方案4】:

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

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

    如果 a 和 b 到达叶子,则应该指向两个具有“非常少”相同位的数字。

    我只是编造了这个算法,不知道它是否正确或如何证明它。但是它应该在 O(n) 的运行时间内。

    【讨论】:

    • 我喜欢制作树的想法,但是如果 A 和 B 都可以到达 0 或 1 节点,你会怎么做?我认为你必须尝试两种可能性,看看哪一种更好。
    • 我觉得没问题,因为A和B是无法区分的,即A -> 1, B -> 0和A -> 0, B -> 1真的是一样的情况对吧?
    • 是的,一旦您迈过第一步,A 和 B 在您的树中的不同位置,因此您移动到哪个位置会有所不同。您走在正确的轨道上,但您只需要稍微复杂一点。
    • 其实复杂度是O(n * log(U)),其中U是给定的最大数,类似于templatetypedef的答案。
    • @DavidGrayson 检查这个。这是您的解决方案的实施形式。 leetcode.com/problems/maximum-xor-of-two-numbers-in-an-array/…
    【解决方案5】:

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

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

    【讨论】:

      【解决方案6】:

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

      【讨论】:

      • 我很好奇你如何证明最大的数必须是具有最大异或结果的对的一部分。我确定这是不正确的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-04-19
      • 2019-02-21
      • 1970-01-01
      • 2017-02-02
      • 2013-03-27
      • 2021-03-07
      • 1970-01-01
      相关资源
      最近更新 更多