Open In App

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:

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);

Output:
[3, 19, 7, 11, 15]

Time Complexity: O(N^2) 

Efficient Approach: This problem can be solved efficiently by divide-and-conquer approach.

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();

Output:
[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.


Article Tags :