给定一个整数数组,需要找到两个元素的异或值最大。
一种朴素的方法是选择每个元素并将其与其他元素进行异或,然后比较结果以找到这对元素。
除此之外,是否有更有效的算法?
给定一个整数数组,需要找到两个元素的异或值最大。
一种朴素的方法是选择每个元素并将其与其他元素进行异或,然后比较结果以找到这对元素。
除此之外,是否有更有效的算法?
O(n lg U)
的算法,其中 U
是最大的数字。这个想法与 user949300 的类似,但具体细节更多一些。1
,并且在拥有此位上的 1
的配对中,您希望选择下一个最高位上有 1
的配对等等。1
位(通过对每个数字执行 O(lg U)
工作来完成,总共需要 O(n lg U)
时间)。现在,将数组分成两部分 - 其中一部分是在该位上有 1
的数字,而另一组则为在该位上为 0
的数字。任何最优解都必须组合一个在第一位上有 1
的数字和一个在该处为 0
的数字,因为这会将一个 1
位放置得尽可能高。任何其他配对都会在那里有一个 0
。1
的数字的 1
和 0
组的配对。为此,将它们中的两个组分成四个组:11
开头的数字
- 以 10
开头的数字
- 以 01
开头的数字
- 以 00
开头的数字11
和 00
组或 10
和 01
组中有任何数字,则它们的异或结果将是理想的(从 11
开始)。因此,如果这些组中的任意一对不为空,则递归计算这些组的理想解决方案,然后返回这些子问题解决方案的最大值。否则,如果两个组都为空,这意味着所有数字的第二位数字必须相同。因此,一个以 1
开头的数字和一个以 0
开头的数字的最佳异或将导致下一个第二位取消,因此我们只需查看第三位即可。1
和分组 0
以及位索引 i
:
- 如果位索引等于位数,则返回在 1
组中的(唯一)数字与在 0
组中的(唯一)数字的异或结果。
- 从这些组构建组 11
、10
、01
和 00
。
- 如果组 11
和组 00
非空,则递归找到从第 i+1
位开始的那两个组的最大异或值。
- 如果组 10
和组 01
非空,则递归查找这两个组的最大异或值,从第 i+1
位开始。
- 如果上述任何101 001 100 011 000 010
我们首先把它们分成1
组和0
组:
101 100
001 011 000 010
现在,我们应用上述算法。我们将其分为组11
、10
、01
和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(log U)位,因此最大递归深度为O(lg U)。在树的每个级别中,每个数字仅出现在一个递归调用中,并且每个递归调用的工作量与0和1组中的数字总数成比例,因为我们需要将它们按位分配。因此,递归树中有O(log U)级别,每个级别都需要进行O(n)工作,从而总工作量为O(n log U)。
希望这可以帮助您!这是一个很棒的问题!可以使用Trie在 O(NlogN)
时间复杂度内解决此问题。
arr[0,1,.....N]
的每个arr[i]
元素
arr[i]
可能的最大异或值。我们知道不同类型的位(0 ^ 1
或1 ^ 0
)的异或始终是1
。因此,在查询每个位时,请尝试遍历持有相反位的节点。这将使特定位1
的结果最大化Xor值。如果没有相反位的节点,则仅遍历相同位节点。arr[i]
插入Trie中。对于N
个元素,我们需要为每个元素进行一次查询(O(logN)
)和一次插入(O(logN)
)。因此,总时间复杂度为O(NlogN)
。
您可以在此线程中找到关于它如何工作的精美图解。
以下是上述算法的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;
}
C
步(整数编码的宽度,在我们的例子中为32
),所以我们的复杂度不应该像插入需要N * C
步加上查询需要N * C
步,结果为O(2 * (N * C)) <=> O(N)
吗? - DVNO忽略符号位,其中一个值必须是具有最高有效位设置的值之一。 除非所有的值都设置了该位,在这种情况下,您需要查找所有值中未设置的下一个最高有效位。因此,您可以通过查看HSB来减少第一个值的可能性。例如,如果可能性为
0x100000
0x100ABC
0x001ABC
0x000ABC
最大对的第一个值必须是0x100000或0x10ABCD中的一个。
@internal Server Error 我认为最小值不一定正确。我没有很好的想法来缩小第二个值。只需选择任何不在可能的第一个值列表中的值即可。 在我的示例中,可以选择0x001ABC或0x000ABC。
如果a和b到达叶子,它们应该指向两个具有“非常少”相同位的数字。
我刚刚想出了这个算法,不知道它是否正确或如何证明它。但是它应该在O(n)运行时间内完成。