【问题标题】:Integer pairs having biwise AND equal to 0按位与等于 0 的整数对
【发布时间】:2019-11-25 02:12:07
【问题描述】:

两个整数 x 和 y 形成一个神奇的对,如果它们的位与结果等于 0。 给定一个整数数组,找出每个数组元素是否与其他数组元素形成一个神奇的对。

输入

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

输出

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

约束

1

我尝试了蛮力。对于每个元素,我检查了该数字的按位与是否为零与数组中存在的任何其他元素。显然,它的时间复杂度为 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] << " ";

【问题讨论】:

  • 请显示一些代码。它有助于更​​好地了解您迄今为止所做的任何尝试。
  • @dorKKnight 添加
  • 你知道它有多慢吗?

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


【解决方案1】:

一种方法是为所有数字创建一个二叉树,例如 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);
    }

}

【讨论】:

  • 如果我理解正确,使用此算法,0x11 和 0x22 将不会被标记为神奇,因为异或这些值会得到 0xEE 和 0xDD,两者都不会在 O 中找到(1) 既不查找 0x11 也不查找 0x22。但是请注意,0x11 和 0x22 实际上给出了零。
  • 好像我错过了这些案例。对于原始 arr 中的每个未设置位,我们必须检查树中的两个位,而不是用 1* 进行异或操作
  • n 与树的高度无关,但与树的大小有关。在最坏的情况下,设置每个数字的第一位和最后一位,这意味着所有根到叶路径的长度为 20(如果您使用无符号整数)。显然,每条路径都没有 20 个不出现在任何其他路径中的唯一节点,但树中的节点数线性依赖于输入数组中的数字数。如果由于左右递归而探索整个树,您将使用O(n)时间。
  • 就像我在前面的评论中提到的,查找的运行时间不取决于路径的长度,而是取决于树的大小,因为代码中的这一行:@987654325 @。这样做的结果是您可能(几乎)遍历整个树。
  • 似乎最坏的情况是 n^2。
【解决方案2】:

首先,很抱歉回答太长了:)

问题:我认为您的蛮力问题在于您每次检查都执行了两次(在两个方向上)。此外,不需要进行大量检查。
您可以通过只进行一次检查(并且只进行必要的检查)来轻松减少迭代次数。

关键思想:你不应该从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 - 检查一个数字是否有一个神奇的对

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

你想检查数组中的每个数字是否在数组中都有一个神奇的对。
在这种情况下,我们不想检查所有可能的神奇对,只有一个匹配就足够了确定一个数字是否有一对。此外,当我们找到匹配项时,我们可以一次设置两个结果(对的每个数字一个)。
您可以看到,这样,我们将能够显着减少迭代次数。

它引导我们进行如下操作:

  • 每对只检查一次
  • 在第一次匹配时停止计算数字
  • 每次匹配确定两个结果 --> 如果已设置则不执行搜索

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

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,

我认为您将能够很容易地使本示例的代码适应您的用例。


希望对你有帮助。

【讨论】:

    【解决方案3】:

    你必须先保存你做的操作。

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

    当你用蛮力做时,你先做

    3 & 6

    在做完所有事情之后

    3 & 年

    你重复

    6 & 3

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

    【讨论】:

    • 不,时间复杂度仍然是 O(N^2)
    • 没关系。顺序很重要,仍然是 N 的平方
    • 您也可以在找到第一个匹配项时停止查找。您可以立即设置ans[j] = 1,并跳过已设置ans 的任何元素。
    • 而渐近复杂度并不能告诉您任何事情首先需要多长时间,而只能告诉您随着输入数量的增加,时间如何缩放。因此,即使更改不会影响渐近复杂度,它仍然可以显着提高运行时间。
    • 好吧,你说它是 N^2,但我尝试用 C++ 做一个简单的解决方案并且成功了。 pastebin.com/U678yjXX
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-04
    • 1970-01-01
    相关资源
    最近更新 更多