关于java:搜索两个数组以进行匹配,没有额外的内存

Searching two arrays for matches, no extra memory

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

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

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

面试官在我解释完HashMap方法后,问我是否能想到一种计算上为o(n)的方法,但不会使用任何额外的内存。我想不出飞行中的任何东西,也找不到解决这个问题的方法。在线性时间内,是否有一种不使用额外内存来查找这些值的方法?

注:我已经在CareerCup上发布了这个问题,但是那里的每个人似乎都不知道我需要它来不使用额外的空间,而且它必须是O(n)计算。

这是我在面试中使用的代码。它可以工作,但对于空间来说不是O(1)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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〔6〕

编辑:同样,当我说没有额外的空间,和O(1),我有点互换使用它们。没有额外的空间,我的意思是小的占位符变量是可以的,但分配新的数组却不行。


在O(n)时间中,没有O(1)空间法来求两个未排序集的交集。

对于范围不受限制的数据类型,最小排序价格为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)时间解。


关键是对两个数组进行适当的排序。我搜索了"就地基数排序",找到了就地基数排序。我相信这个问题是可以解决的,至少对于Java INT[]来说,通过应用这些思想来对每一个数组进行排序,一点一点,然后做明显的扫描。

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
    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;
        }
      }
    }


一个可能的答案类似于HashMap解决方案…如果你知道整数在一个很小的窗口内。它类似于:http://en.wikipedia.org/wiki/bucket_sort

基本上,如果整数被保证在一个固定大小的窗口内(即所有的整数都是1-1000),那么你可以在固定的空间中通过增加每个index=的单元格来实现它,不管你的数字是什么。这与HashMap解决方案完全相同,只是您不需要像HashMap罐那样考虑所有可能的整数,这样可以节省空间。如果不清楚,请在评论中告诉我,我会进一步解释。


我相信这是有可能的地方做与O(1)额外的空间。我使用了额外的假设,即数组中的元素是可变的和可交换的,但是我相信仔细考虑后,对于这个特定的问题,可以去掉可变的假设。

基本思想是就地散列。通过使用O(n)中位数的中位数选择算法,将数组划分为合适的百分位(比如第90个百分位),可以实现就地散列。这将数组分为一小部分(约10%)和一大部分(约90%),它们的元素可以彼此区分(小于或不小于分区元素)。然后可以通过交换从10%部分散列到90%部分。此哈希可用于检测重复项。这是针对阵列10%的每个处理的O(n),所以10次处理仍然是O(n)。我更详细地描述了这一点,尽管有一天我要用一些手势来纠正这个相关的问题。

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