Given an array ‘arr’, the task is to find all possible integers each of which is the bitwise AND of at least one non-empty sub-array of ‘arr’.
Examples:
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]
Naive Approach:
- An array of size ‘N’ contains N*(N+1)/2 sub-arrays. So, for small ‘N’, iterate over all possible sub-arrays and add each of their AND result to a set.
- Since sets do not contain duplicates, it’ll store each value only once.
- Finally, print the contents of the set.
Below is the implementation of the above approach:
// C++ implementation of the approach #include <bits/stdc++.h> 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< int > 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 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<Integer> 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 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# 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< int > set1 = new HashSet< int >();
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 + " " );
}
}
} |
// JavaScript implementation of the approach const A = [11, 15, 7, 19]; const N = A.length; // Set to store all possible AND values. const set = new Set();
// Starting index of the sub-array. for (let i = 0; i < N; i++) {
// Ending index of the sub-array. let res = 2147483647; // Integer.MAX_VALUE
for (let j = i; j < N; j++) {
res &= A[j]; // AND value is added to the set. set.add(res); } } // The set contains all possible AND values. console.log(set); |
[3, 19, 7, 11, 15]
Time Complexity: O(N^2)
Efficient Approach: This problem can be solved efficiently by divide-and-conquer approach.
- Consider each element of the array as a single segment. (‘Divide’ step)
- Add the AND value of all the subarrays to the set.
- Now, for the ‘conquer’ step, keep merging consecutive subarrays and keep adding the additional AND values obtained while merging.
- Continue step 4, until a single segment containing the entire array is obtained.
Below is the implementation of the above approach:
#include <bits/stdc++.h> using namespace std;
// c++ equivalent code of the Python code class Segment{
public :
vector< int > leftToRight;
vector< int > rightToLeft;
void addToLR( int value){
if (leftToRight.size() == 0){
leftToRight.push_back(value);
}
else {
int lastElement = leftToRight[leftToRight.size() - 1];
// value decreased after AND-ing with 'value'
if ((lastElement & value) < lastElement){
leftToRight.push_back(lastElement & value);
}
}
}
void addToRL( int value){
if (rightToLeft.size() == 0){
rightToLeft.push_back(value);
}
else {
int lastElement = rightToLeft[rightToLeft.size() - 1];
// value decreased after AND-ing with 'value'
if ((lastElement & value) < lastElement){
rightToLeft.push_back(lastElement & value);
}
}
}
// copies 'lr' to 'leftToRight'
void copyLR(vector< int > lr){
for ( auto x: lr){
leftToRight.push_back(x);
}
}
// copies 'rl' to 'rightToLeft'
void copyRL(vector< int > rl){
for ( auto x: rl){
rightToLeft.push_back(x);
}
}
}; Segment mergeSegments(Segment a, Segment b) { Segment res; // 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);
for ( auto x: b.leftToRight){
res.addToLR(x);
}
for ( auto x: a.rightToLeft){
res.addToRL(x);
}
return res;
} Segment divideThenConquer(vector< int > ar, int l, int r,set< int > &allPossibleAND) {
// can't divide into further segments
if (l == r){
allPossibleAND.insert(ar[l]);
// Therefore, return a segment containing this single element.
Segment ret;
ret.leftToRight.push_back(ar[l]);
ret.rightToLeft.push_back(ar[l]);
return ret;
} // can be further divided into segments
else {
// recursive calls to divide the array into two halves
Segment left = divideThenConquer(ar, l, floor ((l+r)/2), allPossibleAND);
Segment right = divideThenConquer(ar, floor ((l+r)/2)+1, r, allPossibleAND);
// Now, add all possible AND results, contained in these two segments
for ( auto itr1: left.rightToLeft){
for ( auto itr2: right.leftToRight){
allPossibleAND.insert(itr1 & itr2);
}
}
// 'conquer' step
return mergeSegments(left, right);
}
} int main() {
vector< int > ar = {11, 15, 7, 19};
int n = ar.size();
set< int > allPossibleAND; // call divideThenConquer function
divideThenConquer(ar, 0, n-1, allPossibleAND);
for ( auto x: allPossibleAND){
cout << x << " " ;
}
cout << endl;
} // The code is contributed by Nidhi goel. |
// Java implementation of the approach import java.util.*;
public class CP {
static int ar[];
static int n;
// Holds all possible AND results
static HashSet<Integer> 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<Integer> 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<Integer> leftToRight
= new ArrayList<>();
ArrayList<Integer> 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<Integer> lr)
{
Iterator<Integer> itr = lr.iterator();
while (itr.hasNext())
leftToRight.add(itr.next());
}
// copies 'rl' to 'rightToLeft'
void copyRL(ArrayList<Integer> rl)
{
Iterator<Integer> itr = rl.iterator();
while (itr.hasNext())
rightToLeft.add(itr.next());
}
} |
from typing import List
from collections import defaultdict
# Segment class definition class Segment:
def __init__( self ):
self .leftToRight = [] # left to right segment
self .rightToLeft = [] # right to left segment
def addToLR( self , value):
if len ( self .leftToRight) = = 0 :
self .leftToRight.append(value)
else :
lastElement = self .leftToRight[ - 1 ]
# value decreased after AND-ing with 'value'
if (lastElement & value) < lastElement:
self .leftToRight.append(lastElement & value)
def addToRL( self , value):
if len ( self .rightToLeft) = = 0 :
self .rightToLeft.append(value)
else :
lastElement = self .rightToLeft[ - 1 ]
# value decreased after AND-ing with 'value'
if (lastElement & value) < lastElement:
self .rightToLeft.append(lastElement & value)
# copies 'lr' to 'leftToRight'
def copyLR( self , lr):
self .leftToRight = lr.copy()
# copies 'rl' to 'rightToLeft'
def copyRL( self , rl):
self .rightToLeft = rl.copy()
# recursive function which adds all possible AND results to 'allPossibleAND' def divideThenConquer(ar: List [ int ], l: int , r: int , allPossibleAND: set ):
# can't divide into further segments
if l = = r:
allPossibleAND.add(ar[l])
# Therefore, return a segment containing this single element.
ret = Segment()
ret.leftToRight.append(ar[l])
ret.rightToLeft.append(ar[l])
return ret
# can be further divided into segments
else :
# recursive calls to divide the array into two halves
left = divideThenConquer(ar, l, (l + r) / / 2 , allPossibleAND)
right = divideThenConquer(ar, (l + r) / / 2 + 1 , r, allPossibleAND)
# Now, add all possible AND results, contained in these two segments
for itr1 in left.rightToLeft:
for itr2 in right.leftToRight:
allPossibleAND.add(itr1 & itr2)
# 'conquer' step
return mergeSegments(left, right)
# returns the resulting segment after merging segments 'a' and 'b' # 'conquer' step def mergeSegments(a: Segment, b: Segment) - > Segment:
res = 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)
for value in b.leftToRight:
res.addToLR(value)
for value in a.rightToLeft:
res.addToRL(value)
return res
# driver code def main():
ar = [ 11 , 15 , 7 , 19 ]
n = len (ar)
allPossibleAND = set ()
# initialize with default values
leftToRight = defaultdict( int )
rightToLeft = defaultdict( int )
# call divideThenConquer function
divideThenConquer(ar, 0 , n - 1 , allPossibleAND)
print (allPossibleAND)
if __name__ = = '__main__' :
main()
|
// JavaScript equivalent code of the Python code function Segment() {
this .leftToRight = []; // left to right segment
this .rightToLeft = []; // right to left segment
} Segment.prototype.addToLR = function (value) {
if ( this .leftToRight.length === 0) {
this .leftToRight.push(value);
} else {
let lastElement = this .leftToRight[ this .leftToRight.length - 1];
// value decreased after AND-ing with 'value' if ((lastElement & value) < lastElement) {
this .leftToRight.push(lastElement & value);
} } }; Segment.prototype.addToRL = function (value) {
if ( this .rightToLeft.length === 0) {
this .rightToLeft.push(value);
} else {
let lastElement = this .rightToLeft[ this .rightToLeft.length - 1];
// value decreased after AND-ing with 'value' if ((lastElement & value) < lastElement) {
this .rightToLeft.push(lastElement & value);
} } }; Segment.prototype.copyLR = function (lr) {
this .leftToRight = lr.slice();
}; Segment.prototype.copyRL = function (rl) {
this .rightToLeft = rl.slice();
}; function divideThenConquer(ar, l, r, allPossibleAND) {
// can't divide into further segments if (l === r) {
allPossibleAND.add(ar[l]); // Therefore, return a segment containing this single element. let ret = new Segment();
ret.leftToRight.push(ar[l]); ret.rightToLeft.push(ar[l]); return ret;
} // can be further divided into segments
else {
// recursive calls to divide the array into two halves
let left = divideThenConquer(ar, l, Math.floor((l+r)/2), allPossibleAND);
let right = divideThenConquer(ar, Math.floor((l+r)/2)+1, r, allPossibleAND);
// Now, add all possible AND results, contained in these two segments
for (let itr1 of left.rightToLeft) {
for (let itr2 of right.leftToRight) {
allPossibleAND.add(itr1 & itr2);
}
}
// 'conquer' step
return mergeSegments(left, right);
} } function mergeSegments(a, b) {
let 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); for (let value of b.leftToRight) {
res.addToLR(value);
} for (let value of a.rightToLeft) {
res.addToRL(value);
} return res;
} // driver code function main() {
let ar = [11, 15, 7, 19]; let n = ar.length; let allPossibleAND = new Set(); // call divideThenConquer function
divideThenConquer(ar, 0, n-1, allPossibleAND); console.log(allPossibleAND); } main(); |
[19, 3, 7, 11, 15]
Analysis: The major optimization in this algorithm is realizing that any array element can yield a maximum of 30 distinct integers (as 30 bits are needed to hold 1e9). Confused?? Let’s proceed step by step. Let us start a sub-array from the ith element which is A[i]. As the succeeding elements are AND-ed with Ai, the result can either decrease or remain same (because a bit will never get changed from ‘0’ to ‘1’ after an AND operation). In the worst case, A[i] can be 2^31 – 1 (all 30 bits will be ‘1’). As the elements are AND-ed, atmost 30 distinct values can be obtained because only a single bit might get changed from ‘1’ to ‘0’ in the worst case, i.e. 111111111111111111111111111111 => 111111111111111111111111101111 So, for each ‘merge’ operation, these distinct values merge to form another set having atmost 30 integers. Therefore,
Worst Case Time Complexity for each merge can be O(30 * 30) = O(900).
Time Complexity: O(900*N*logN). PS: The time complexity can seem too high, but in practice, the actual complexity lies around O(K*N*logN), where, K is much less than 900. This is because, the lengths of the ‘prefix’ and ‘suffix’ arrays are much less when ‘l’ and ‘r’ are quite close.