给定一个数组“ 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进行“与”运算,结果可能减小或保持不变(因为在“与”运算之后,位永远不会从“ 0”变为“ 1”)。
在最坏的情况下,A [i]可能是2 ^ 31 – 1(所有30位都为1)。由于将元素进行AND运算,因此最多可获得30个不同的值,因为在最坏的情况下,只有一个位会从’1’变为’0’,
即111111111111111111111111111111 => 111111111111111111111111111101111
因此,对于每个“合并”操作,这些不同的值合并以形成具有最多30个整数的另一个集合。
因此,每个合并的最坏情况时间复杂度可以为O(30 * 30)= O(900)。
时间复杂度: O(900 * N * logN)。
PS:时间复杂度似乎过高,但实际上,实际复杂度在O(K * N * logN)附近,其中K远远小于900。这是因为’prefix’和’当’l’和’r’非常接近时,后缀’数组要少得多。