Numbers that are bitwise AND of at least one non-empty sub-array
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:
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<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); } } |
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< 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 + " " ); } } } |
[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:
// 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()); } } |
[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.
Recommended Posts:
- Numbers whose bitwise OR and sum with N are equal
- Check whether bitwise OR of N numbers is Even or Odd
- Russian Peasant (Multiply two numbers using bitwise operators)
- Leftover element after performing alternate Bitwise OR and Bitwise XOR operations on adjacent pairs
- Find subsequences with maximum Bitwise AND and Bitwise OR
- Sum of bitwise OR of all subarrays
- Bitwise and (or &) of a range
- Bitwise OR (or | ) of a range
- Sum of bitwise AND of all subarrays
- Bitwise Operators in C/C++
- Bitwise Sieve
- Sum of bitwise OR of all possible subsets of given set
- Sum of bitwise AND of all possible subsets of given set
- Sum of Bitwise-OR of all subarrays of a given Array | Set 2
- Number of subarrays have bitwise OR >= K
If you like GeeksforGeeks and would like to contribute, you can also write an article using contribute.geeksforgeeks.org or mail your article to contribute@geeksforgeeks.org. See your article appearing on the GeeksforGeeks main page and help other Geeks.
Please Improve this article if you find anything incorrect by clicking on the "Improve Article" button below.
Improved By : Kirti_Mangal