用目标按位与值查找子数组

时间:2019-04-12 18:55:11

标签: algorithm data-structures

给出大小为A的数组N和整数P,找到子数组B = A[i...j],使得i <= j计算子数组元素的按位值说K = B[i] & B[i + 1] & ... & B[j]
输出|K-P|的所有可能值中的K的最小值。

5 个答案:

答案 0 :(得分:1)

您熟悉Find subarray with given sum problem吗?我提出的解决方案使用与链接中有效解决方案相同的方法。强烈建议您先阅读它,然后再继续。

首先让我们注意到,子数​​组K越长,它就会越小,因为两个数字之间的&运算符只能创建一个较小的数字。

因此,如果我有一个从ij的子数组,而我想使其K变小,我会添加更多元素(现在该子数组来自{{1} }到i),如果我想扩大j + 1,我将删除元素(Ki + 1)。

如果我们回顾一下j的解决方案,就会发现我们可以轻松地将其转换为问题-给定的总和为Find subarray with given sum,求和就像使用​​K运算符一样,但是更多元素较小&,因此我们可以翻转总和的比较。

此问题告诉您解决方案是否存在,但是,只要您保持目前发现的最小差异,您也可以解决问题。

修改

如注释中所述,如果所有数字均为正,则此解决方案为真,如果并非所有数字均为正,则解决方案略有不同。

请注意,如果并非所有数字均为负数,则K将为正数,因此要找到负数K,我们可以考虑使用算法中的负数,而不是使用算法如上所示。

答案 1 :(得分:1)

这是一种准线性方法,假设数组的元素具有恒定的位数。

矩阵K[i,j] = A[i] & A[i + 1] & ... & A[j]的行单调递减(忽略矩阵的下三角)。这意味着K[i,:]与搜索参数P之间的差的绝对值是单峰的,并且存在最小值(不一定是 the 最小值,因为同一最小值可能会多次出现,但是那么他们将连续执行此操作),并可以在ternary search的O(log n)时间中找到(假设对K元素的访问可以固定时间排列)。对每一行重复此操作,并输出最低最小值的位置,使其达到O(n log n)。

在小于行大小的时间内执行最小行搜索需要隐式访问矩阵K的元素,这可以通过创建b前缀和数组来实现,其中A元素的每一位。然后,可以通过计算所有b个单位范围的求和并将它们与范围的长度进行比较来找到范围与,每次比较都给出范围与的一位。这需要进行O(nb)预处理,并赋予O(b)(根据我在开始时所做的假设如此恒定)对K的任意元素的访问。

我曾希望绝对差的矩阵可以是允许使用SMAWK算法的Monge矩阵,但事实并非如此,我找不到找到实现该特性的方法。

答案 2 :(得分:1)

还有另一种准线性算法,将yonlif 查找具有给定总和问题的子数组解决方案与Harold的思想混合,以计算K[i,j];因此,如果内存消耗大,我不会使用预处理。我使用计数器来跟踪位并计算2N的{​​{1}}个值,每个值最多花费K。由于O(log N)通常小于字长(log N),因此它比线性B算法要快。

仅用〜O(NB)个字就可以计算N个数字的位数:

enter image description here

因此,您只能使用log N个运算来计算A[i]&A[i+1]& ... &A[I+N-1]

这里是管理计数器的方式:如果

  • log Ncounter,并且
  • C0,C1, ...CpCk

然后Ck0,Ck1, ...CkmCpq ... C1q,C0q的第q位中等于1的位数的二进制表示。

位级实现(在python中);所有位都是并行管理的。

{A[i],A[i+1], ... ,A[j-1]}

和算法:

def add(counter,x):
    k = 0
    while x :
        x, counter[k] = x & counter[k], x ^ counter[k]
        k += 1

def sub(counter,x):
    k = 0
    while x :
        x, counter[k] = x & ~counter[k], x ^ counter[k]
        k += 1

def val(counter,count):  # return A[i] & .... & A[j-1] if count = j-i.  
    k = 0
    res = -1
    while count:
       if count %2 > 0 : res &= counter[k]
       else: res &= ~counter[k]
       count //= 2
       k += 1
    return res

def solve(A,P): counter = np.zeros(32, np.int64) # up to 4Go n = A.size i = j = 0 K=P # trig fill buffer mini = np.int64(2**63-1) while i<n : if K<P or j == n : # dump buffer sub(counter,A[i]) i += 1 else: # fill buffer add(counter,A[j]) j += 1 if j>i: K = val(counter, count) X = np.abs(K - P) if mini > X: mini = X else : K = P # reset K return mini valsubadd,所以整个过程是O(ln N)

测试:

O(N ln N)

numba编译版本(用n = 10**5 A = np.random.randint(0, 10**8, n, dtype=np.int64) P = np.random.randint(0, 10**8, dtype=np.int64) %time solve(A,P) Wall time: 0.8 s Out: 452613036735 装饰4个函数)快了200倍(5毫秒)。

答案 3 :(得分:0)

Yonlif答案是错误的。

Find subaray with given sum解决方案中,我们有一个循环用于进行子构造。

while (curr_sum > sum && start < i-1)
    curr_sum = curr_sum - arr[start++];        

由于有no inverse个逻辑AND运算符,因此我们无法重写此行,也不能直接使用此解决方案。

有人会说,每次增加滑动窗口的下限时,我们都可以重新计算总和(这将导致我们O(n^2)的时间复杂度),但是这种解决方案不起作用(我将提供最后的代码和计数器示例。

这是适用于O(n^3)

的蛮力解决方案
unsigned int getSum(const vector<int>& vec, int from, int to) {
    unsigned int sum = -1;
    for (auto k = from; k <= to; k++)
        sum &= (unsigned int)vec[k];
    return sum;
}

void updateMin(unsigned int& minDiff, int sum, int target) {
    minDiff = std::min(minDiff, (unsigned int)std::abs((int)sum - target));
}

// Brute force solution: O(n^3)
int maxSubArray(const std::vector<int>& vec, int target) {
    auto minDiff = UINT_MAX;
    for (auto i = 0; i < vec.size(); i++) 
        for (auto j = i; j < vec.size(); j++)                           
            updateMin(minDiff, getSum(vec, i, j), target);          
    return minDiff;
}

以下是C ++中的O(n^2)解决方案(感谢B.M的答案):其想法是更新当前总和,而不是每两个索引调用一次getSum。您还应该查看B.M的答案,因为它包含了早期夸张的条件。这是C ++版本:

int maxSubArray(const std::vector<int>& vec, int target) {
    auto minDiff = UINT_MAX;
    for (auto i = 0; i < vec.size(); i++) {
        unsigned int sum = -1;
        for (auto j = i; j < vec.size(); j++) {
            sum &= (unsigned int)vec[j];
            updateMin(minDiff, sum, target);
        }
    }               
    return minDiff;
}

这不是带有滑动窗口的解决方案::这是Yonlif回答的想法,其中对O(n^2)

中的和进行了预计算
int maxSubArray(const std::vector<int>& vec, int target) {
    auto minDiff = UINT_MAX;
    unsigned int sum = -1;
    auto left = 0, right = 0;

    while (right < vec.size()) {
        if (sum > target)
            sum &= (unsigned int)vec[right++];
        else                                
            sum = getSum(vec, ++left, right);       
        updateMin(minDiff, sum, target);            
    }
    right--;
    while (left < vec.size()) {
        sum = getSum(vec, left++, right);
        updateMin(minDiff, sum, target);            
    }   
    return minDiff;
}

此解决方案的问题是,我们跳过了一些实际上可能是最好的序列。

输入:vector = [26,77,21,6]target = 5

当77&21 = 5时,输出应该为零,但是滑动窗口方法无法找到那个值,因为它将首先考虑窗口[0..3],然后增加下限,而无法考虑窗口[1 ..]。 2]。

如果某人有线性或对数线性解决方案有效,则可以发布。

答案 4 :(得分:0)

这是我写的一个解决方案,它花费了O(n^2)阶的时间复杂度。 以下代码段是用Java编写的。

class Solution{
    public int solve(int[] arr,int p){
        int maxk = Integer.MIN_VALUE;
        int mink = Integer.MAX_VALUE;
        int size = arr.length;

        for(int i =0;i<size;i++){
            int temp = arr[i];
            for(int j = i;j<size;j++){
                temp &=arr[j];
                if(temp<=p){
                    if(temp>maxk)
                        maxk = temp;
                }
                else{
                    if(temp < mink)
                        mink = temp;
                }
            }
        }
        int min1 = Math.abs(mink -p);
        int min2 = Math.abs(maxk -p);
        return ( min1 < min2 ) ? min1 : min2;
    }
}

这是一种简单的蛮力方法,其中有2个数字让我们说x和y,从而找到x <= k和y> = k,其中x和y是不同的K = arr [i]&arr [i + 1 ]&... arr [j]其中i <= j表示不同的i,j表示x,y。 答案将只是| x-p |的最小值。和| y-p | 。