【问题标题】:Find missing numbers in array, time complexity O(N), space complexity O(1)查找数组中缺失的数字,时间复杂度 O(N),空间复杂度 O(1)
【发布时间】:2016-04-10 17:07:34
【问题描述】:

给定一个包含 n 个唯一整数 0

例子:

find_missing([0]) = [1]

find_missing([0, 2, 4]) = [1, 3, 5] # 因为所有数字都是[0, 1, 2, 3, 4, 5]

find_missing([]) = []

find_missing([0, 1, 4, 5]) = [2, 3, 6, 7] # 因为所有的数字都是[0, 1, 2, 3, 4, 5, 6, 7]

怪癖是关于需求的:

时间复杂度 O(n) - 但是应该有一些与输入大小无关的固定常数 C,这样数组的每个元素都被写入/读取

空间复杂度 O(1) - 你可以修改初始数组,但是 sorted(initial_array) 必须等于 sorted(array_after_executing_program) 并且你不能在这个数组中存储范围 [0, 2n) 之外的整数(想象它是一个uint32_t 数组)。

我看到了很多复杂的解决方案,但后来我发现了这个:

public void printNotInArr(int[] arr) {
    if(arr == null)
        return null;

    int len = arr.length;
    int max = 2 * len;

    for(int i = 0; i < len; i++) {
        System.out.println(max - arr[i] - 1);
    }
}

我相信这是最好的解决方案,但我不确定。我想知道为什么那行不通。

【问题讨论】:

  • 由于它没有按照您的要求执行,因此根本不是解决方案。尝试传入0, 3。你会得到3, 0,显然它根本不会输出丢失的数字。
  • 输入是否保证排序,就像在所有示例中一样?
  • 这个问题和stackoverflow.com/questions/3492302/…一样吗?
  • 这与那个问题不一样,גלעד ברקן,因为另一个问题只删除了一个数字。这是一个完全不同的问题,有一个简单的解决方案。这里的这个不同,可能缺少多个数字。其他问题的简单解决方案在这里不起作用。
  • 我现在看到,如果两个元素相互补充,它就会中断。 IE。如果 Max = 3,arr=[0, 3] 0+3 = 3 = Max。 arr=[1,2] -> rs[2,1] 和 2+1 = 3.

标签: arrays algorithm sorting


【解决方案1】:

正如@LasseV.Karlsen 指出的那样,[0,3] 是一个简单的反例,它显示了该解决方案如何不起作用。然而,这是一个非常简单的解决方案(在 Python 中):

def show_missing(l):
  n = len(l)

  # put numbers less than n into the proper slot
  for i in range(0,n):
    while l[i]<n and l[i]!=i:
      j = l[i]
      l[i] = l[j]
      l[j] = j
  for i in range(0,n):
    if l[i]!=i:
      print('Missing %s'%i)

  # put numbers greater than n into the proper slot
  for i in range(0,n):
    while l[i]>=n and l[i]!=i+n:
      j = l[i]
      l[i] = l[j-n]
      l[j-n] = j
  for i in range(0,n):
    if l[i]!=i+n:
      print('Missing %s'%(i+n))

这个想法很简单。我们首先重新排列元素,以便每个小于 n 的值 j 都存储在索引 j 处。然后我们可以遍历数组,轻松挑选出低于 n 的那些缺失的。

然后我们重新排列元素,以便每个大于或等于 n 的值 j 都存储在索引 j-n 处。同样,我们可以遍历数组并轻松挑选出大于或等于 n 的那些缺失的。

由于只使用了几个局部变量,因此满足 O(1) 空间复杂度。

由于嵌套循环,O(n) 时间复杂度有点难以看出,但不难证明我们永远不会交换超过 n 个元素,因为一个新元素被放入它的适当位置每次交换的地方。

由于我们只是交换了数组的元素,所以也满足了所有原始元素仍在数组中的要求。

【讨论】:

  • 你能解释一下为什么你先做小于 N 的数字,然后再做大于 N 的数字吗?在第二个循环中,为什​​么是 "l[i]!=i+n" 而不是 "l[i]!=i"
  • @user117325:我们不能同时处理较大和较小的数字,因为会有冲突。例如,如果 l==[0,2].
  • @user117325:在第二个循环中,我们只考虑值 >= n。 n 存储在索引 0 中,n+1 存储在索引 1 中,等等。这就是我们检查 l[i]!=l[i+n] 的原因。
  • @YvesDaoust 的代码(上图)在 O(n) 交换方面做了同样的事情,但没有将它分成两部分(小于 N,大于 N)。还有另一个例子会导致你描述的冲突吗?他的解决方案可能缺少这一点
  • @user117325:我不认为这个答案是有效的,因为它依赖于扩展原始数组。
猜你喜欢
  • 2015-05-25
  • 2016-06-21
  • 1970-01-01
  • 1970-01-01
  • 2021-04-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多