【问题标题】:Searching two arrays for matches, no extra memory在两个数组中搜索匹配项,没有额外的内存
【发布时间】:2012-10-29 04:26:09
【问题描述】:

前几天我接受了亚马逊的面试,他们问我的一个问题与以下问题有关。

给定 2 个整数数组,包含任意数量的正负元素,找出出现在两个数组中的数字。

我可以很容易地用HashMaps 解决这个问题,所以它会有O(n) 的计算复杂度,但不幸的是这也会有O(n) 的空间复杂度。这可以通过遍历每个数组中的所有元素来完成,而无需额外的内存,但这将是O(n^2)

面试官在我解释完HashMap 方法后,问我是否可以想出一个计算时间为 O(n),但不会使用任何额外内存的方法。我无法在飞行中想到任何,也无法找到解决方案。有没有一种方法可以在不使用额外内存的情况下在线性时间内找到这些值?

注意:我已经在 CareerCup 上发布了这个问题,但那里的每个人似乎都没有得到这样的概念,即我需要它来不使用额外的空间,并且它必须是 O(n) 计算。

这是我在采访中使用的代码。它有效,但空间不是 O(1)。

import java.util.*;
public class ArrayFun {
    public static void main(String[] args) {

        int[] a = {1,2,3,4};
        int[] b = {2,5,6,7,3,2,2,2,2,1,2,2,2,2};
        ArrayList<Integer> matches = ArrayFun.findMatches(a,b);
        for (int i = 0;i<matches.size();++i) {
            System.out.println(matches.get(i));
        }
    }

    public static ArrayList<Integer> findMatches(int[] a, int[] b) {
        HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();
        ArrayList<Integer> matches = new ArrayList<Integer>();
        for (int i = 0;i<a.length;++i) {
            map.put(a[i],0);
        }
        for (int i = 0;i<b.length;++i) {
            if (map.get(b[i]) != null && map.get(b[i]) == 0) {
                map.put(b[i],1);
                matches.add(b[i]);
            }
        }
        return matches;
    }
}

此代码将返回

1,2,3

编辑:当我说没有额外的空间和 O(1) 时,我有点交替使用它们。没有额外空间,我的意思是小的占位符变量很好,但分配新数组不是。

【问题讨论】:

  • 您可以在O(n log n) 中通过对两个数组进行适当的排序来完成此操作...这可能不是他们想要的,但是
  • 这只有在两个数组排序后才有可能。否则是不可能的。
  • 我假设两个数组都没有排序。是这样吗?
  • @durron597 没有一种不会使用额外空间(堆或堆栈)的高效排序方法。另外,O(n log n)典型情况,但最坏的情况通常是O(n2)
  • @MarkoTopolnik:随机快速排序非常好。显然,所有快速排序算法的最坏情况都是O(n^2)

标签: java arrays algorithm


【解决方案1】:

没有 O(1) 空间方法可以在 O(n) 时间内找到两个未排序集的交集。

对于无限范围的数据类型,最小排序价格为O(n ln n)。

对于具有有限范围的数据类型,基数排序提供了在 O(n ln n' n") 时间内进行就地基数排序的能力,其中 n 是数据的大小,n' 是数字可以表示的值的个数,并且 n" 与检查两个值是否在同一个基数组中的成本有关。可以降低 n" 时间价格以换取 O(ln n) 空间价格。

在 32 位整数的特殊情况下,n' 为 2^32 且 n" 为 1,因此这将折叠为 O(n) 并为数十亿的记录集提供了一个成功的解决方案。

对于无限大小的整数,n' 和 n" 排除了通过基数的 O(n) 时间解。

【讨论】:

  • 这就是我的结论。但如果没有,他们为什么要我试一试呢?
  • 也许他们想看看你是否会尝试 B.S.当您不知道答案,或者您是否可以给出明确的“不可能”答案时。
  • 如果任何排序必须通过比较排序完成,我会同意。但是,如果数组是 Java int,我们不限于比较排序。
  • @MZimmerman6 问题可能是看看您是否知道 \Omega(n log n) 最小排序时间仅适用于比较排序。如果您有一个有限范围的结构化类型,例如 Java int,那么还有其他类型是 O(n)。特别是,请参阅我的答案中的就地基数排序。
  • 排序并不能真正帮助我,我仍然需要对所有内容进行索引。我不会吗?
【解决方案2】:

一个可能的答案类似于HashMap 解决方案... 如果您知道整数在一个非常小的窗口内。类似于:http://en.wikipedia.org/wiki/Bucket_sort

基本上,如果整数保证在某个恒定大小的窗口内(即它们都是 1-1000),那么您可以通过递增 index = 无论您的数字是多少,在恒定空间中进行操作。这与HashMap 解决方案完全相同,只是您不需要像HashMap 那样考虑所有可能的整数,这样可以节省空间。如果不清楚,请在 cmets 中告诉我,我会进一步解释。

【讨论】:

    【解决方案3】:

    我相信这可以通过O(1) 额外 空间就地 进行。我利用了数组中的元素既可变又可交换的附加假设,但我相信通过仔细考虑,可以针对这个特定问题删除可变性假设。

    基本思想是就地散列。就地散列可以通过使用O(n)median-of-medians selection algorithm 围绕一个合适的百分位(例如第90 个百分位)对数组进行分区来实现。这将阵列分为一小部分(约 10%)和大部分(约 90%),它们的元素可相互区分(小于或不小于分区元素)。然后,您可以通过交换从 10% 部分哈希到 90% 部分。此散列可用于检测重复项。这是O(n),每次处理10%的数组,所以做10次还是O(n)。我更详细地描述了这一点,尽管我想在某一天在this related question. 上更正一下。

    对于这个特定的问题,您需要进行 3 次就地散列。首先在每个单独的数组上删除重复项。然后,在表示组合数组的包装器上(如果索引小于数组 1 的长度,则索引到数组 1,否则索引到数组 2)以报告重复项。

    【讨论】:

      【解决方案4】:

      关键是对两个数组进行就地排序。我搜索了“就地基数排序”,找到了In-Place Radix Sort。我相信这个问题是可以解决的,至少对于 Java int[],通过应用这些想法对每个数组进行排序,一点一点地进行,然后进行明显的扫描。

      顺便说一句,我认为问题代码中问题的正确输出是 1、2、3。

      这是我的实现,基于对引用问题的回答:

          public class ArrayMatch {
            public static void main(String[] args) {
              int[] a = { 4, 1, 2, 3, 4 };
              int[] b = { 2, 5, 6, 7, 3, 2, 2, 2, 2, 1, 2, 2, 2, 2 };
              System.out.print("Original problem");
              printMatches(a, b);
              System.out.println();
      
              int[] a1 = { 4, 1, -1234, 2, 3, 4, Integer.MIN_VALUE };
              int[] b1 = { -1234, 2, 5, 6, 7, 3, 2, 2, 2, 2, 1, 2, 2, 2, 2 , Integer.MIN_VALUE, Integer.MAX_VALUE};
              System.out.print("With negatives");
              printMatches(a1, b1);
              System.out.println();
      
            }
      
            // Print all matching elements between the two arrays.
            private static void printMatches(int[] a, int[] b) {
              if (a.length == 0 || b.length == 0) {
                return;
              }
      
              sort(a);
              sort(b);
      
              int i = 0;
              int j = 0;
              while (true) {
                while (a[i] < b[j]) {
                  i++;
                  if (i == a.length) {
                    return;
                  }
                }
                while (a[i] > b[j]) {
                  j++;
                  if (j == b.length) {
                    return;
                  }
                }
      
                if (a[i] == b[j]) {
                  System.out.print(" " + a[i]);
      
                  do {
                    i++;
                  } while (i < a.length && a[i - 1] == a[i]);
      
                  do {
                    j++;
                  } while (j < b.length && b[j - 1] == b[j]);
                }
      
                if (i == a.length || j == b.length) {
                  return;
                }
              }
            }
      
            // In place radix sort.
            private static void sort(int[] in) {
              // Flip the sign bit to regularize the sort order
              flipBit(in, 31);
              sort(in, 0, in.length, 31);
              // Flip back the sign bit back to restore 2's complement
              flipBit(in, 31);
            }
      
            /**
             * Sort a subarray, elements start through end-1 of in, according to the
             * values in firstBit through 0.
             * 
             * @param in
             * @param start
             * @param end
             * @param firstBit
             */
            private static void sort(int[] in, int start, int end, int firstBit) {
              if (start == end) {
                return;
              }
              int mask = 1 << firstBit;
              int zeroCount = 0;
              for (int i = start; i < end; i++) {
                if ((in[i] & mask) == 0) {
                  zeroCount++;
                }
              }
      
              int elements = end - start;
              int nextZeroIndex = start;
              int nextOneIndex = start + zeroCount;
      
              int split = nextOneIndex;
      
              if (zeroCount > 0 && zeroCount < elements) {
                while (nextZeroIndex < split) {
                  if ((in[nextZeroIndex] & mask) != 0) {
                    // Found a one bit in the zero area, look for its partner in the one
                    // area
                    while ((in[nextOneIndex] & mask) != 0) {
                      nextOneIndex++;
                    }
                    int temp = in[nextZeroIndex];
                    in[nextZeroIndex] = in[nextOneIndex];
                    in[nextOneIndex] = temp;
                    nextOneIndex++;
                  }
                  nextZeroIndex++;
                }
      
              }
      
              if (firstBit > 0) {
                sort(in, start, split, firstBit - 1);
                sort(in, split, end, firstBit - 1);
              }
      
            }
      
            private static void flipBit(int[] in, int bitNo) {
              int mask = 1 << bitNo;
              for (int i = 0; i < in.length; i++) {
                in[i] ^= mask;
              }
            }
          }
      

      【讨论】:

        猜你喜欢
        • 2013-12-21
        • 2021-11-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-11-19
        相关资源
        最近更新 更多