双向AND等于0的整数对

时间:2019-07-16 10:51:29

标签: c++ algorithm bit-manipulation time-complexity

如果两个整数x和y的按位与结果等于0,则它们构成一个神奇的对。 给定一个整数数组,请为每个数组元素查找它是否与其他数组元素形成了魔幻对。

输入

输入的第一行包含一个整数T,该整数表示测试用例的数量。 每个测试用例的第一行都有一个整数N,表示给定数组中元素的数量。 第二行包含N个单个以空格分隔的整数a1,a2,...,它表示给定数组的元素。

输出

对于每个测试用例,在一行中打印N个空格分隔的整数。 如果ai与给定数组的任何其他元素形成一个魔幻对,则ans'i应该等于1。否则ans'i为0。

约束

1 <= N,Ai <= 10 ^ 6

我尝试过蛮力。对于每个元素,我检查此数字的按位与是否为零,或者是否与数组中存在的任何其他元素相同。显然,它的时间复杂度为O(N ^ 2),我的大多数测试用例都超时了

此问题在这里:https://www.hackerearth.com/challenges/test/netapp-codenet-2017/algorithm/d2d1f6a92c6740278682e88ed42068a4/

有人能建议我一种更好的方法或算法,使其超过时间限制吗?

暴力代码:

int n;
cin >> n;
int a[n];
for (int i = 0; i < n; i++)
    cin >> a[i];
int ans[n];
for (int i = 0; i < n; i++)
    for (int j = 0; j < n; j++)
        if (a[i] & a[j] == 0)
            ans[i] = 1;
for (int i = 0; i < n; i++)
    cout << ans[i] << " ";

3 个答案:

答案 0 :(得分:5)

一种方法是为所有数字创建一个二叉树,如Trie。

例如,如果您有数组3 6 2 9 10,则二进制数组看起来像

arr = 11,110,10,1001,1010,树想要

                                     root
                                /             \
                               0               1
                                 \           /   \ 
                                  1         0     1
                                 / \       / 
                                0   1     0   
                                 \         \   
                                  1         1  

如果我们遍历二进制数组中的每个元素,则与条件匹配的数字(对于元素中的每个设置位应为0,对于元素中的未设置位应为0或1)。

现在,我们只需要将这些位遍历到树中即可。如果我们能够做到,那么至少存在一个满足条件的数字。

时间复杂度O(N)。 原因:-有n个数字。每个数字均为32位二进制长度。创建新节点将使用O(1)。因此,O(32N)=> O(N)。 inv_arr的时间相同。

注意:尝试将数字转换为32位二进制数字,因为它将覆盖范围内指定的所有数字。否则会导致问题。这里的6和9构成了一个魔幻对,但是inv_arr中的0110不能被遍历,并且由于无法完成最左边0的遍历,因此将导致不存在任何魔幻对。如果所有数字都用相同长度的二进制表示,则遍历树将给出正确的答案。

代码

public class BinaryNode {

    char c;
    public BinaryNode left;
    public BinaryNode right;

    public BinaryNode(char c) {
        this.c = c;
    }

}

public class BinaryTree {

    public BinaryNode root;

    public BinaryTree(char c) {
        root = new BinaryNode(c);
    }

    public void addToTree(String s) {
        BinaryNode node = this.root;
        int length = s.length();
        for (int i = s.length()-1; i >= 0; i--) {
            BinaryNode newNode;
            if (s.charAt(i) == '0') {
                 newNode = addCharToTree(node.left, s.charAt(i));
                 node.left = newNode;
            } else {
                newNode = addCharToTree(node.right, s.charAt(i));
                node.right = newNode;
            }
            node = newNode;
        }
    }

    private BinaryNode addCharToTree(BinaryNode node, char c) {
        if (node == null)
            return new BinaryNode(c);
        return node;
    }


}


public class Solution {

  private static void findMagicalPairs(List<Integer> list) {
        // for creating 32 char long binary string list
        List<String> binaryNumberList = list.stream()
                .map(num -> Long.toBinaryString( Integer.toUnsignedLong(num) | 0x100000000L ).substring(1))
                .collect(Collectors.toList());
        // dummy character as root
        BinaryTree binaryTree = new BinaryTree('c');
        binaryNumberList.forEach(binaryTree::addToTree);
        List<Boolean> booleanList = binaryNumberList.stream()
                                     .map(s -> hasMagicalPair(s, binaryTree.root))
                                     .collect(Collectors.toList());
    }

    private static boolean hasMagicalPair(String s, BinaryNode node) {
        if (s == null || s.length() == 0)
            return true;
        if (node == null)
            return false;
        String substring = s.substring(0, s.length() - 1);
        if (s.charAt(s.length()-1) == '1')
            return hasMagicalPair(substring, node.left) ;
        return hasMagicalPair(substring, node.left) || hasMagicalPair(substring, node.right);
    }

}

答案 1 :(得分:1)

首先,很长的回答很抱歉:)

问题::我认为您的强力问题在于您每次执行两次检查(双向检查)。而且,很多检查都是不必要的。
通过只进行一次检查(并且只进行必要的检查),可以轻松地减少迭代次数。

主要思想:您不应从0开始内循环。

注意::以下第一部分仅介绍第二部分,而第二部分是回答您问题的部分。

此处提供的整个代码仅用于说明陈述的想法,仅此而已。


1-查找所有可能的魔法对

在这里,我们试图在给定向量中找到所有可能的魔术对,以避免多次检查同一对。

解决方案可能是:

std::vector<std::pair<int, int>> magical_pairs(const std::vector<int> & data)
{
    std::vector<std::pair<int, int>> result;

    for(size_t i = 0; i < data.size()-1; ++i) // Stop at second to last
    {
        for(size_t j = i+1; j < data.size(); ++j) // Start from i+1 and not 0
        {
            if((data[i] & data[j]) == 0)
                result.push_back(std::make_pair(data[i], data[j]));
        }
    }

    return result;
}

这样,您只需检查一次所有可能的对

根据我的说法,如果您想获得所有可能的魔法对,您所降低的复杂性不会比仅检查一次所有可能的对降低
但是如果有人拥有更好的解决方案,我将非常有兴趣听到(阅读)。

您可以通过以下方式运行示例:

std::vector<int> input_array {3, 12, -6, 27, 8, 18, -66, 47, 11}; // input example

for(const std::pair<int, int> & mp : magical_pairs(input_array))
    std::cout << mp.first << " : " << mp.second << std::endl;

此示例的结果:

  

3:12
  3:8
  12:18
  8:18


2-检查数字是否具有魔幻对

现在,我们知道如何避免检查已检查的对,我们将重用相同的原理来实现所需的功能。

您要检查数组中的每个数字在数组中是否都具有魔术对。
在这种情况下,我们不想检查所有可能的魔术对,只有一个匹配就足够了确定一个数字是否有一对。此外,当找到匹配项时,我们可以一次设置两个结果(该对中的每个数字一个)。
您可以看到,通过这种方式,我们将能够显着减少迭代次数。< / p>

它导致我们继续进行以下操作:

  • 每对仅检查一次
  • 在第一次比赛中停止对数字的评估
  • 确定每场比赛有两个结果->如果已经设置,则不执行搜索

知道这一点,解决方案可能是:

std::vector<bool> has_magical_pair(const std::vector<int> & data)
{
    std::vector<bool> result(data.size(), false);

    for(size_t i = 0; i < data.size()-1; ++i) // From 0 to second to last
    {
        if(!result[i]) // search for a magical pair only if not already found
        {
            for(size_t j = i+1; j < data.size(); ++j) // From i+1 to last
            {
                if((data[i] & data[j]) == 0)
                {
                    // Set two results at a time
                    result[i] = true;
                    result[j] = true;
                    break; // Exit the inner loop at first match
                }
            }
        }
    }

    return result;
}

这样,您将比蛮力方法更有效

您可以通过以下方式运行示例:

std::vector<int> input_array {3, 12, -6, 27, 8, 18, -66, 47, 11};

for(bool hmp : has_magical_pair(input_array))
    std::cout << hmp << ", ";
std::cout << std::endl;

此示例的结果:

  

1,1,0,0,1,1,0,0,0,

我认为您将能够很轻松地使此示例的代码适应您的用例。


希望它能对您有所帮助。

答案 2 :(得分:-1)

您必须保存您首先执行的操作。

在该示例中,您有3 6 2 9 10

当你用蛮力去做的时候,你首先要做

  

3&6

在完成所有

之后
  

3&y

您重复

  

6&3

。如果您找到避免重复此操作的方法,则将解决问题。