📌  相关文章
📜  至少一个非空子数组的按位 AND 的数字

📅  最后修改于: 2021-09-16 11:17:22             🧑  作者: Mango

给定一个数组“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’ 非常接近时,后缀数组要少得多。