给定一个数组“arr”,任务是找到所有可能的整数,每个整数都是“arr”的至少一个非空子数组的按位与。
例子:
Input: arr = {11, 15, 7, 19}
Output: [3, 19, 7, 11, 15]
3 = arr[2] AND arr[3]
19 = arr[3]
7 = arr[2]
11 = arr[0]
15 = arr[1]
Input: arr = {5, 2, 8, 4, 1}
Output: [0, 1, 2, 4, 5, 8]
0 = arr[3] AND arr[4]
1 = arr[4]
2 = arr[1]
4 = arr[3]
5 = arr[0]
8 = arr[2]
天真的方法:
- 大小为“N”的数组包含 N*(N+1)/2 个子数组。因此,对于小的“N”,迭代所有可能的子数组并将它们的每个 AND 结果添加到一个集合中。
- 由于集合不包含重复项,因此它只会存储每个值一次。
- 最后,打印集合的内容。
下面是上述方法的实现:
C++
// C++ implementation of the approach
#include
using namespace std;
int main()
{
int A[] = {11, 15, 7, 19};
int N = sizeof(A) / sizeof(A[0]);
// Set to store all possible AND values.
unordered_set s;
int i, j, res;
// Starting index of the sub-array.
for (i = 0; i < N; ++i)
// Ending index of the sub-array.
for (j = i, res = INT_MAX; j < N; ++j)
{
res &= A[j];
// AND value is added to the set.
s.insert(res);
}
// The set contains all possible AND values.
for (int i : s)
cout << i << " ";
return 0;
}
// This code is contributed by
// sanjeev2552
Java
// Java implementation of the approach
import java.util.HashSet;
class CP {
public static void main(String[] args)
{
int[] A = { 11, 15, 7, 19 };
int N = A.length;
// Set to store all possible AND values.
HashSet set = new HashSet<>();
int i, j, res;
// Starting index of the sub-array.
for (i = 0; i < N; ++i)
// Ending index of the sub-array.
for (j = i, res = Integer.MAX_VALUE; j < N; ++j) {
res &= A[j];
// AND value is added to the set.
set.add(res);
}
// The set contains all possible AND values.
System.out.println(set);
}
}
Python3
# Python3 implementation of the approach
A = [11, 15, 7, 19]
N = len(A)
# Set to store all possible AND values.
Set = set()
# Starting index of the sub-array.
for i in range(0, N):
# Ending index of the sub-array.
res = 2147483647 # Integer.MAX_VALUE
for j in range(i, N):
res &= A[j]
# AND value is added to the set.
Set.add(res)
# The set contains all possible AND values.
print(Set)
# This code is contributed by Rituraj Jain
C#
// C# implementation of the approach
using System;
using System.Collections.Generic;
class CP {
public static void Main(String[] args)
{
int[] A = {11, 15, 7, 19};
int N = A.Length;
// Set to store all possible AND values.
HashSet set1 = new HashSet();
int i, j, res;
// Starting index of the sub-array.
for (i = 0; i < N; ++i)
{
// Ending index of the sub-array.
for (j = i, res = int.MaxValue; j < N; ++j)
{
res &= A[j];
// AND value is added to the set.
set1.Add(res);
}
}
// displaying the values
foreach(int m in set1)
{
Console.Write(m + " ");
}
}
}
[3, 19, 7, 11, 15]
时间复杂度: O(N^2)
高效方法:这个问题可以通过分而治之的方法有效地解决。
- 将数组的每个元素视为单个段。 (“划分”步骤)
- 将所有子数组的 AND 值添加到集合中。
- 现在,对于“征服”步骤,继续合并连续的子数组并继续添加合并时获得的附加 AND 值。
- 继续步骤 4,直到获得包含整个数组的单个段。
下面是上述方法的实现:
// Java implementation of the approach
import java.util.*;
public class CP {
static int ar[];
static int n;
// Holds all possible AND results
static HashSet allPossibleAND;
// driver code
public static void main(String[] args)
{
ar = new int[] { 11, 15, 7, 19 };
n = ar.length;
allPossibleAND = new HashSet<>(); // initialization
divideThenConquer(0, n - 1);
System.out.println(allPossibleAND);
}
// recursive function which adds all
// possible AND results to 'allPossibleAND'
static Segment divideThenConquer(int l, int r)
{
// can't divide into
//further segments
if (l == r)
{
allPossibleAND.add(ar[l]);
// Therefore, return a segment
// containing this single element.
Segment ret = new Segment();
ret.leftToRight.add(ar[l]);
ret.rightToLeft.add(ar[l]);
return ret;
}
// can be further divided into segments
else {
Segment left
= divideThenConquer(l, (l + r) / 2);
Segment right
= divideThenConquer((l + r) / 2 + 1, r);
// Now, add all possible AND results,
// contained in these two segments
/* ********************************
This step may seem to be inefficient
and time consuming, but it is not.
Read the 'Analysis' block below for
further clarification.
*********************************** */
for (int itr1 : left.rightToLeft)
for (int itr2 : right.leftToRight)
allPossibleAND.add(itr1 & itr2);
// 'conquer' step
return mergeSegments(left, right);
}
}
// returns the resulting segment after
// merging segments 'a' and 'b'
// 'conquer' step
static Segment mergeSegments(Segment a, Segment b)
{
Segment res = new Segment();
// The resulting segment will have
// same prefix sequence as segment 'a'
res.copyLR(a.leftToRight);
// The resulting segment will have
// same suffix sequence as segment 'b'
res.copyRL(b.rightToLeft);
Iterator itr;
itr = b.leftToRight.iterator();
while (itr.hasNext())
res.addToLR(itr.next());
itr = a.rightToLeft.iterator();
while (itr.hasNext())
res.addToRL(itr.next());
return res;
}
}
class Segment {
// These 'vectors' will always
// contain atmost 30 values.
ArrayList leftToRight
= new ArrayList<>();
ArrayList rightToLeft
= new ArrayList<>();
void addToLR(int value)
{
int lastElement
= leftToRight.get(leftToRight.size() - 1);
// value decreased after AND-ing with 'value'
if ((lastElement & value) < lastElement)
leftToRight.add(lastElement & value);
}
void addToRL(int value)
{
int lastElement
= rightToLeft.get(rightToLeft.size() - 1);
// value decreased after AND-ing with 'value'
if ((lastElement & value) < lastElement)
rightToLeft.add(lastElement & value);
}
// copies 'lr' to 'leftToRight'
void copyLR(ArrayList lr)
{
Iterator itr = lr.iterator();
while (itr.hasNext())
leftToRight.add(itr.next());
}
// copies 'rl' to 'rightToLeft'
void copyRL(ArrayList rl)
{
Iterator itr = rl.iterator();
while (itr.hasNext())
rightToLeft.add(itr.next());
}
}
[19, 3, 7, 11, 15]
分析:
该算法的主要优化是实现任何数组元素最多可以产生 30 个不同的整数(因为需要 30 位来保存 1e9)。使困惑??让我们一步一步来。
让我们从第 i 个元素 A[i] 开始一个子数组。由于后续元素与 Ai 进行 AND 运算,结果可以减少或保持不变(因为在 AND 运算之后,位永远不会从“0”变为“1”)。
在最坏的情况下,A[i] 可以是 2^31 – 1(所有 30 位都将是“1”)。由于元素是 AND 运算的,因此最多可以获得 30 个不同的值,因为在最坏的情况下,只有一个位可能从“1”变为“0”,
即 111111111111111111111111111111 => 111111111111111111111111101111
因此,对于每个“合并”操作,这些不同的值合并以形成另一个最多包含 30 个整数的集合。
因此,每次合并的最坏情况时间复杂度可以是 O(30 * 30) = O(900)。
时间复杂度: O(900*N*logN)。
PS:时间复杂度可能看起来太高了,但实际上,实际复杂度在 O(K*N*logN) 左右,其中,K 远小于 900。这是因为,’prefix’ 和 ‘当 ‘l’ 和 ‘r’ 非常接近时,后缀数组要少得多。