Maximise the AND

Maximise the AND

本文关键字:AND the Maximise      更新时间:2023-10-16

给定一个n个非负整数数组:A1, A2,…,an。如何找到一对整数Au, Av(1≤u <v≤N)使得(Au和Av)尽可能大。>

示例:设N=4,数组为[2 4 8 10],答案为8

解释
2 and 4 = 0
2 and 8 = 0
2 and 10 = 2
4 and 8 = 0
4 and 10 = 0
8 and 10 = 8

如果N可以达到10^5,怎么做呢?我有O(N^2)个解。但效率不高

代码:

for(int i=0;i<n;i++){
    for(int j=i+1;j<n;j++){
        if(arr[i] & arr[j] > ans)
        {
            ans=arr[i] & arr[j];
        }
    }
}

加快速度的一种方法是利用这样一个事实,即如果任意两个数字中设置了任何高位,那么这两个数字的与将始终大于使用低位的任何组合。

因此,如果你按位数排序,你可能会大大减少操作的次数。

为了有效地找到最高有效位,GCC有一个内置的固有函数:__builtin_clz(unsigned int x),它返回最高有效位的索引。(其他编译器也有类似的内在特性,至少在x86上可以转换成一条指令)。

const unsigned int BITS = sizeof(unsigned int)*8; // Assuming 8 bit bytes.
// Your implementation over.
unsigned int max_and_trivial( const std::vector<unsigned int> & input);    
// Partition the set.
unsigned int max_and( const std::vector<unsigned int> & input ) {
    // For small input, just use the trivial algorithm.
    if ( input.size() < 100 ) { 
        return max_and_trivial(input);
    }        
    std::vector<unsigned int> by_bit[BITS];
    for ( auto elem : input ) {
         unsigned int mask = elem;
         while (mask) { // Ignore elements that are 0.
             unsigned int most_sig = __builtin_clz(mask);
             by_bits[ most_sig ].push_back(elem);
             mask ^= (0x1 << BITS-1) >>  most_sig;
         }
    }
    // Now, if any of the vectors in by_bits have more 
    // than one element, the one with the highest index 
    // will include the largest AND-value.
    for ( unsigned int i = BITS-1; i >= 0; i--) {
        if ( by_bits[i].size() > 1 ) {
             return max_and_trivial( by_bits[i]);
        }
    }
    // If you get here, the largest value is 0.
    return 0;
}

该算法的最坏情况运行时间为0 (N*N),但平均而言它的性能应该要好得多。您还可以通过在搜索较小的向量时重复分区步骤来进一步提高性能(只需记住忽略分区步骤中最重要的位,这样做会将性能提高到最坏的情况O(N))。

保证输入数据中没有重复将进一步提高性能。

  1. 按降序排序数组。
  2. 取前两个数字。如果它们都在2的两个连续幂之间(比如2^k和2^(k+1)),那么你可以删除所有小于2^k的元素。
  3. 从剩余的元素中减去2^k。
  4. 重复步骤2和3,直到数组中的元素数为2。

注意:如果你发现只有最大的元素在2^k和2^(k+1)之间,并且第二大元素小于2^k,那么你不删除任何元素,只从最大的元素中减去2^k。

还可以确定元素在系列{1,2,4,8,16,…}可以在O(log(log(MAX)))时间内完成,其中MAX是数组中最大的数字。

我没有测试这个,我也不打算测试。O(N)内存和O(N)复杂度。

#include <vector>
#include <utility>
#include <algorithm>
using namespace std;

/*
 * The idea is as follows:
 * 1.) Create a mathematical set A that holds integers.
 * 2.) Initialize importantBit = highest bit in any integer in v
 * 3.) Put into A all integers that have importantBit set to 1.
 * 4.) If |A| = 2, that is our answer. If |A| < 2, --importantBit and try again. If |A| > 2, basically
 *     redo the problem but only on the integers in set A.
 *
 * Keep "set A" at the beginning of v.
 */
pair<unsigned, unsigned> find_and_sum_pair(vector<unsigned> v)
{
    // Find highest bit in v.
    int importantBit = 0;
    for(auto num : v)
        importantBit = max(importantBit, highest_bit_index(num));
    // Move all elements with imortantBit to front of vector until doing so gives us at least 2 in the set.
    int setEnd;
    while((setEnd = partial_sort_for_bit(v, importantBit, v.size())) < 2 && importantBit > 0)
        --importantBit;
    // If the set is never sufficient, no answer exists
    if(importantBit == 0)
        return pair<unsigned, unsigned>();
    // Repeat the problem only on the subset defined by A until |A| = 2 and impBit > 0 or impBit  = 0
    while(importantBit > 1)
    {
        unsigned secondSetEnd = partial_sort_for_bit(v, --importantBit, setEnd);
        if(secondSetEnd >= 2)
            setEnd = secondSetEnd;
    }
    return pair<unsigned, unsigned>(v[0], v[1]);
}
// Returns end index (1 past last) of set A
int partial_sort_for_bit(vector<unsigned> &v, unsigned importantBit, unsigned vSize)
{
    unsigned setEnd = 0;
    
    unsigned mask = 1<<(importantBit-1);
    for(decltype(v.size()) index = 0; index < vSize; ++index)
        if(v[index]&mask > 0)
            swap(v[index], v[setEnd++]);
    
    return setEnd;
}

unsigned highest_bit_index(unsigned i)
{
    unsigned ret = i != 0;
    while(i >>= 1)
        ++ret;
    return ret;
}

我又遇到了这个问题,并以一种不同的方式解决了它(对我来说更容易理解):

unsigned findMaxAnd(vector<unsigned> &input) {
    vector<unsigned> candidates;
    for(unsigned mask = 1<<31; mask; mask >>= 1) {
        for(unsigned i : input)
            if(i&mask)
                candidates.push_back(i);
        if (candidates.size() >= 2)
            input = move(candidates);
        candidates = vector<unsigned>();
    }
    
    if(input.size() < 2) {
        return 0;
    return input[0]&input[1]; 
}

这是一个O(N * log MAX_A)的解决方案:

1)让我们贪婪地构造答案,从最高位到最低位迭代。

2)为了做到这一点,我们可以维持一个S个当前适合的数的集合。最初,它由数组中的所有数字组成。我们还假设最初ANS = 0。

3)现在让我们从最高到最低遍历所有的位。假设当前位是b

4)如果S中第B位值为1的元素数大于1,则在不改变ANS中更高位的值的情况下,有可能在这个位置上有1,因此我们应该在ANS中添加2^B,并从S中删除所有该位值为0的元素(它们不再适合)。

5)否则在这个位置不可能得到1,所以我们不改变S和ANS,继续下一位