【问题标题】:How to make space complexity as O(1)如何使空间复杂度为 O(1)
【发布时间】:2016-04-21 12:10:19
【问题描述】:

我正在尝试回答以下问题:您有一个整数数组,这样每个整数都会出现奇数次,其中 3 个除外。找出这三个数字。

到目前为止,我使用蛮力方法:

 public static void main(String[] args) {
    // TODO Auto-generated method stub

    int number[] = { 1, 6, 4, 1, 4, 5, 8, 8, 4, 6, 8, 8, 9, 7, 9, 5, 9 };
    FindEvenOccurance findEven = new FindEvenOccurance();
    findEven.getEvenDuplicates(number);

  }

  // Brute force
  private void getEvenDuplicates(int[] number) {

    Map<Integer, Integer> map = new HashMap<Integer, Integer>();

    for (int i : number) {

      if (map.containsKey(i)) {
        // a XOR a XOR a ---- - -- - - odd times = a
        // a XOR a ---- -- -- --- - even times = 0
        int value = map.get(i) ^ i;
        map.put(i,value);
      } else {
        map.put(i, i);
      }
    }

    for (Entry<Integer, Integer> entry : map.entrySet()) {

      if (entry.getValue() == 0) {
        System.out.println(entry.getKey());
      }

    }
  }

它工作正常,但效率不高。

o/p:

1
5
6
8

但问题指定我们需要在 O(1) 空间和 O(N) 时间复杂度内执行此操作。对于我的解决方案,时间复杂度是 O(N),但空间也是 O(N)。有人可以建议我用 O(1) 空间做这件事的更好方法吗?

谢谢。

【问题讨论】:

  • “除了 3 个”,你的例子有 4 个!?!
  • 实际上第一条语句与代码和输出冲突。因此,当其他解决方案显示找到除非配对之外的所有整数的方法时,一些解决方案尝试找到三个非配对整数。请编辑您的问题并严格说明您想要什么!
  • 由于您必须再次遍历地图以检索结果,时间复杂度不会超过 O(N) 吗?任何你可以就地排序的方法。时间会增加到 n*log(n) 或它的一些变化,但你的空间复杂度会降低到零!
  • 我当然希望问题不在于数字(对于在 N 之前固定的任何基数)——这个例子没有给出任何线索。
  • 衡量你可以做什么:discussion of scalability.

标签: algorithm data-structures bit-manipulation time-complexity space-complexity


【解决方案1】:

考虑例如允许的数字大小为 4 位,这意味着允许的数字范围从 0 到 24-1这是一个常数16,对于每个可能的输入,我们遍历所有数组并且xor这个数字的出现,如果xor的结果为零,我们添加当前值到整体结果。此解决方案是 O(16N),即 O(N),并且仅使用一个额外变量来评估当前数字的异或,即 O(1) 在空间复杂度方面。

我们可以将此方法扩展到我们的原始问题,但它在运行时间复杂度方面将有一个非常大的常数,这将与原始输入中允许的位数成正比。

我们可以通过遍历所有元素并找到所有输入数据的最高有效位来增强这种方法,假设它是第 10th 位,那么我们的运行时间复杂度将变为 O(210N) 这也是 O(N)

在下图中可以找到另一个增强功能,但仍具有前面讨论的最坏情况复杂性。

最后我相信,对于这个问题还有另一个更好的解决方案,但我决定分享我的想法。

编辑:

图片中的算法可能不太清楚,这里是对算法的一些解释。
它从尝试根据那里的位划分元素的想法开始,换句话说,将这些位作为过滤器,在每个阶段对划分的元素进行异或运算,直到异或结果为零,然后值得检查这一组一个,因为它肯定会包含至少一个所需的输出。或者如果两个咨询过滤器的大小相同,我们将停止此过滤器,下面的示例会更清楚。
输入:1,6,4,1,4,5,8,8,4,6,8,8,9,7,9,5,9
我们首先根据最低有效位划分元素。
1st 位零:6,4,4,8,8,4,6,8 ,8
6 xor 4 xor 4 xor 8 xor 8 xor 4 xor 6 xor 8 xor 8 = 4
所以我们将继续根据第 2nd 位划分这个组。
1st 位零2 nd 零位 : 4,4,4,8,8,8,8
4 xor 4 xor 4 xor 8 xor 8 xor 8 xor 8 xor 8 = 4。
所以我们将继续根据第 3rd 位来划分这个组。
1st 位 02 nd 位零3rd 位零 : 8,8,8,8
8 异或 8 异或 8 异或 8 = 0
因此,我们将遍历此过滤器下的每个元素,因为 xor 的结果为零,我们将在我们的结果中添加 8。
1st 位零2nd 位 03rd 位 1:4,4,4
4 xor 4 xor 4 = 4
1st 位零2nd 位零3rd 位 14th 位 0 : 4,4,4
4 xor 4 xor 4 = 4。
所以我们将在此停止,因为此过滤器包含与前一个过滤器相同的大小
现在我们将回到 1st 和 2nd
1st 位零的过滤器和 2nd 位一:6,6
6 异或 6 = 0。
因此,我们将遍历此过滤器下的每个元素,因为 xor 的结果为零,到目前为止,我们将在结果中添加 6。
现在我们回到 1st bit
1st bit one 的过滤器:9,5,9,7,9 ,1,1
现在我们将在此过滤器下继续进行与之前相同的过程。
有关完整示例,请参见上图。

【讨论】:

  • 如果我不眯着眼睛太用力,你正在做一个“自定义”计数排序。
  • 是的,这类似于计数排序,但我首先认为它试图根据那里的位分配元素,请参阅答案中的image,这是我首先想到的。
  • 如果你有从 0 到 15 的数字,那么 O(16*n) 是 o(n^2)。只看树就清楚地知道时间复杂度不是 o(n)。
  • @ChristopherOezbek 允许的数字是从 0 到 15,但没有说不允许重复,所以你可以有 1000 个数字,但值在 0 到 15 的范围内。
【解决方案2】:

不幸的是,如果我们使用严格意义上的空间,则不可能以 O(1) 空间和 O(n) 复杂度实现这样的解决方案,即 O(1) 空间受输入中使用的最大空间限制数组。

在弱空间感中,一个任意大整数仍然适合 O(1),您可以将您的计数器编码为这个整数的位。从所有位设置为 1 开始。当您在输入数组中遇到数字 n 时,切换第 n 位。最后剩下的所有位为 1 表示遇到偶数次的 3 个数字。

【讨论】:

  • 关于您的第一条评论,我认为在复杂性方面将“O(1) 空间”理解为“O(1) 空间超过输入本身”是标准的>”。否则,像L 这样的复杂性类将没有意义。关于您的第二条评论,以这种方式访问​​任意大整数通常与复杂性中使用的标准 RAM 模型相反,在该模型中,单位时间内只能访问大小为 log(n) 的整数。
  • @Edgar:是的,这是最简单的方法。
  • @EdgarRokyan:很抱歉,这个问题没有解决方案可以满足给定的 O 约束。如果问题是相反的:除了三个整数之外的所有整数都存在偶数次,那么我们可以获得更好的解决方案(仍然不是 O(1) 空间)。
  • 我认为我没有仔细阅读您的解决方案。你明确地谈到了约束。因此考虑具有大元素的数组是没有意义的,因为在这种情况下,额外的大整数不适合 O(1) 空间。
  • 但是我们解决了不同的问题,因为 OP 没有仔细指定她想要什么。如果我们尝试找到三个不成对的整数,似乎我可以在没有大整数或额外数组的情况下解决这个问题。
【解决方案3】:

您对问题的概述与示例不匹配。您说您在问题中寻找 3 个整数,但示例显示 4。

如果没有额外的限制,我不确定这是否可行。在我看来,最坏情况的大小复杂度总是至少 O(N-6) => O(N) 没有排序列表和完整的整数集。

如果我们从排序数组开始,那么是的,很简单,但是没有指定这个约束。自己对数组进行排序将过于时间或空间复杂。

【讨论】:

    【解决方案4】:

    有两种方法可以查看您的问题。

    第一种方式,作为一个无限整数集合的数学问题,似乎无法解决。

    第二种方法,作为一个有限整数集的计算问题,你已经解决了(恭喜!)。为什么 ?因为存储空间以 MAX_INT 为界,与 N 无关。

    NB 一个明显的空间优化是只存储一次值,删除之前的偶数值,你将获得一半的空间。

    关于@Lashane 和@SGM1 的其他答案:它们也解决了“计算”问题,但在大多数现实世界的场景中,可以说效率不如你的。为什么 ?因为他们预先分配了一个 512MB 的数组,而不是根据数组中不同值的数量按比例分配。由于数组可能使用远小于 MAX_INT 的不同值,因此即使您为每个值存储 32 位而不是 1,您也可能使用远小于 512MB 的空间。这是使用 32 位整数,预置位更多位分配的数组将呈指数增长,OTOH 您的解决方案仅取决于数组中的实际值,因此不受系统位数(即最大 int 值)的影响。

    另请参阅 thisthis 以获得更好(更少空间)的算法。

    【讨论】:

    • 我们需要某种方法来评估算法的实际复杂性,因此我们通常将自己限制为非无限整数来这样做。我们定义了最大可能的整数 (MAXSIZE)。在这种情况下,整数
    • @BenJackson 没关系,我只是说除了 Edgar 的提议之外,所有解决方案都在空间中使用 O(MAXSIZE),而原始提议(具有讽刺意味)可能在实践中使用更少的空间。 NB Edgar 的解决方案是在我第一次回答之后添加的。
    【解决方案5】:

    我花了一些时间解决这个问题。似乎我找到了解决方案。无论如何,我相信,该社区将帮助我检查下面列出的想法。

    首先,我声称当非成对整数的个数等于12时,我们可以解决这个问题。在 1 非配对整数的情况下,我们只需要找到所有数组元素的 XOR 即可得到答案。在 2 非配对整数的情况下,解决方案变得更加复杂。但这已经在前面讨论过了。例如你可以找到它here

    现在让我们尝试解决非配对整数个数等于3的问题。

    一开始我们也计算所有元素的XOR。让我们将其表示为 X

    考虑 X 中的第 i-th 位。我假设它等于0。如果它等于1,接下来的过程实际上是相同的,我们只需将0更改为1,反之亦然。

    所以,如果 X 位中的 i-th 等于 0 我们有两种可能的情况。一种情况是所有非成对整数在第 i-th 位中都有 0。另一种情况是一个不成对的整数在i-th位有0,而两个不成对的整数在1 >i-th 位。该语句基于简单的 XOR 运算属性。所以我们有一个或三个不成对的整数,在 i-th 位中有 0

    现在让我们将所有元素分为两组。第一组用于在 i-th 位位置具有 0 的整数,第二组用于在 i 中具有 1 的整数-th 位位置。此外,我们的第一组包含一个或三个非配对整数,在 i-th 位中带有“0”。

    我们如何才能获得第一组中一定数量的非配对整数?我们只需要计算第二组中所有元素的XOR。如果它等于 0,那么所有不成对的整数都在第一组中,我们需要检查另一个 i。在其他情况下,只有一个非配对整数在第一组中,另外两个在第二组中,我们可以使用此答案开头的方法分别解决这两组的问题。

    关键的观察是存在 i 使得一个非配对整数具有与 i-th 不同的 i-th 位其他两个非配对整数的位。在这种情况下,非配对整数在两组中。这是基于这样一个事实,如果没有这样的 i ,那么非成对整数中所有位置的位都是相似的,并且它们彼此相等。但是根据问题陈述是不可能的。

    无需任何额外内存即可实施此解决方案。总复杂度是线性的,有一些常数取决于数组元素中的位数。

    【讨论】:

    • XOR 意味着已知位数,因此这不是数学整数无限集的解决方案。这是一个有效的“计算”解决方案,但在这种情况下,我认为其他解决方案也是 O(1),参见我的回答,你不同意吗?
    • @Ilya 我认为我们无法解决纯粹的数学问题。在实践中,我们假设 XOR 操作具有 O(1) 复杂度,因为总位数是有限的。我只是想表明我们可以解决这个问题,而不需要任何额外的巨大数组,这些数组的大小取决于位数......
    • 我同意,但我的主要观点是原始的“蛮力”解决方案也是 O(1),因此它也是暴露的有效解决方案问题。你不同意吗?
    • 是的,也许,她没有说。但我的第二点是,OP 解决方案可以说比答案中的“改进”解决方案更好。因此,如果您的解决方案有效,我会给他们评分 1) 您的 2) OP 3) Lashane & SGM1。在位数固定的情况下,所有 O(1)。
    • 我认为这个解决方案可以找到奇数的整数,而不是偶数。链接的答案会找到不重复的数字。 例如奇怪。该问题要求输入偶数的整数。
    【解决方案6】:

    我以稍微不同的方式使用 Lashane 的建议来回答答案:

    字符 negBits[268435456]; // 2 ^ 28 = 2 ^ 30(负整数个数)/8(字符大小) 字符 posBits[268435456]; // 同上,除了正数 int number[] = { 1, 6, 4, 1, 4, 5, 8, 8, 4, 6, 8, 8, 9, 7, 9, 5, 9 }; 对于(整数:数字){ 如果(数字 > 1); } 别的 { posBits[num &lt&lt 4] ^= ((num & 0xf) >> 1); // 获取要处理的 rite 字符 // 切换位以表示整数值。 } } // 现在是困难的部分,在所有切换之后找到什么值: for (int i = 0; i &lt Integer.MAX_VALUE; i++){ if (negBits[i &lt&lt 4] & ((i & 0xf) >> 1)){ System.out.print(" " + (-i - 1)); } if (posBits[i &lt&lt 4] & ((i & 0xf) >> 1)){ System.out.print(" " + i); } }

    根据 cmets 中的讨论,以下几点值得注意:

    • 假定 Java 为 32 位。
    • Java 数组具有 Integer.MAX_INT 的固有限制

    【讨论】:

    • 我对 Lashane 的回答有同样的反对意见。此 for 循环 for (int num : number) 必须包含一个计数器,该计数器通过数组的不同索引 N 进行计数,并将值分配给 num。即使您认为int 的大小是恒定的,该计数器的大小也必须至少为log N 位,否则循环是不可能的。如果使用可用内存可以表示的状态少于N,那么您将无法跟踪下一个数字是什么,或者在正确的时间退出循环。
    • 您是否认为您的解决方案使用了O(1) 的额外内存?
    • @ChrisBeck 这个 for 循环不应该包含计数器,它使用迭代器,它可以在内部使用几乎无限长的 BIgDecimal 计数器
    • @Lashane,不,这甚至行不通,当它变得太大时,它会失去精度,然后当你尝试增加计数器时,它会丢弃你的增量
    • 没有“Java”标签,是不是只有Java?
    猜你喜欢
    • 1970-01-01
    • 2017-09-01
    • 2015-05-25
    • 2018-03-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多