给定一个整数数组,你必须找到两个XOR最大的元素。
有天真的方法 - 只需挑选每个元素并使用其他元素进行xoring,然后比较结果以找到该对。
除此之外,有没有有效的算法?
答案 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组和00组或10组和01组中有任何数字,则它们的XOR是理想的(从11开始)。因此,如果这些组中的任何一组不为空,则递归地从这些组计算理想解,然后返回那些子问题解的最大值。否则,如果两个组都为空,则表示所有数字在其第二个位置必须具有相同的数字。因此,以1开头的数字和从0开始的数字的最佳XOR将最终使下一个第二位取消,所以我们应该看第三位。
这给出了以下递归算法,从两个由MSB划分的数字组开始,给出了答案:
要启动算法,您实际上只需将初始组中的数字划分为两组 - 具有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)
这可以使用Trie在O(NlogN)
时间复杂度中解决。
arr[i]
的每个arr[0, 1, ..... N]
元素
arr[i]
可能的最大xor值。我们知道不同类型的位(0 ^ 1
或1 ^ 0
)的xor总是1
。因此在查询每个位时,尝试遍历持有相反位的节点。这将使特定位1
导致最大化xor值。如果没有相反位的节点,则只遍历相同的位节点。arr[i]
插入trie。对于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到达叶子,则应指向两个数字,其中“极少”相同的位。
我刚刚制作了这个算法并且不知道它是否正确或如何证明它。但是它应该在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。