给出大小为A
的数组N
和整数P
,找到子数组B = A[i...j]
,使得i <= j
计算子数组元素的按位值说K = B[i] & B[i + 1] & ... & B[j]
。
输出|K-P|
的所有可能值中的K
的最小值。
答案 0 :(得分:1)
您熟悉Find subarray with given sum problem吗?我提出的解决方案使用与链接中有效解决方案相同的方法。强烈建议您先阅读它,然后再继续。
首先让我们注意到,子数组K
越长,它就会越小,因为两个数字之间的&
运算符只能创建一个较小的数字。
因此,如果我有一个从i
到j
的子数组,而我想使其K
变小,我会添加更多元素(现在该子数组来自{{1} }到i
),如果我想扩大j + 1
,我将删除元素(K
到i + 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
个数字的位数:
因此,您只能使用log N
个运算来计算A[i]&A[i+1]& ... &A[I+N-1]
。
这里是管理计数器的方式:如果
log N
是counter
,并且C0,C1, ...Cp
是Ck
,然后Ck0,Ck1, ...Ckm
是Cpq ... 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
,val
和sub
是add
,所以整个过程是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 | 。