【问题标题】:Given an array of integers, find the first missing positive integer in linear time and constant space给定一个整数数组,在线性时间和常数空间中找到第一个丢失的正整数
【发布时间】:2018-12-23 01:48:21
【问题描述】:

换句话说,找到数组中不存在的最小正整数。该数组也可以包含重复数和负数。 Stripe 在其编程采访中提出了这个问题。我已经设计了一个解决方案,如下所示:

#include<bits/stdc++.h>
using namespace std;

int main(){
    int arr[]={1,-1,-5,-3,3,4,2,8};
    int size= sizeof(arr)/sizeof(arr[0]);
    sort(arr, arr+size);
    int min=1;

    for(int i=0; i<size; i++){
        if(arr[i]>min) break;
        if(arr[i]==min) min=min+1;
    }
    cout<<min;
    return 0;
}

这里,我是先对数组进行排序,然后对数组进行一次遍历。在遍历数组之前,我已经将一个名为“min”的变量初始化为 1。现在,在遍历数组时,当我们得到一个等于 min 的整数时,我们只需增加 min 的值。这可确保 min 变量保存尚未发生的最新最小正整数。 你能想出更好的方法吗?提前致谢。

【问题讨论】:

  • 你不能用常数空间对数组进行线性时间排序。
  • @DillonDavis 实际上你可以,如果数字的范围是有界的。
  • @Henry,刚刚用谷歌搜索了它——计数排序确实有一个就地变体。我的立场是正确的。
  • 其实你可以,如果数字是“特殊的”(即整数)stackoverflow.com/questions/2352313/…
  • 您应该明确指定该数组是可修改的。

标签: arrays algorithm sorting array-algorithms


【解决方案1】:

假设数组可以修改,

  1. 我们将数组分成两部分,第一部分只包含正数。假设我们的起始索引为0,结束索引为end(不包括)。

  2. 我们从索引0end 遍历数组。我们取该索引处元素的绝对值 - 假设值为x

    1. 如果x &gt; end 我们什么都不做。
    2. 如果不是,我们将索引x-1 处的元素的符号设为负数。 (澄清:我们不切换符号。如果值为正,则变为负。如果为负,则保持负。在伪代码中,这将类似于 if (arr[x-1] &gt; 0) arr[x-1] = -arr[x-1] 而不是arr[x-1] = -arr[x-1].)
  3. 最后,我们再次从索引0 遍历数组到end。如果我们在某个索引处遇到一个正元素,我们输出index + 1。这就是答案。但是,如果我们没有遇到任何正元素,则意味着整数1end 出现在数组中。我们输出end + 1

也可能是所有数字都为非正数,使end = 0。输出 end + 1 = 1 仍然正确。

所有步骤都可以在O(n) 时间和使用O(1) 空间内完成。

示例:

Initial Array:            1 -1 -5 -3 3 4 2 8
Step 1 partition:         1 8 2 4 3 | -3 -5 -1, end = 5

在第 2 步中,我们更改正数的符号以跟踪哪些整数已经出现。例如这里array[2] = -2 &lt; 0,它表明2 + 1 = 3已经出现在数组中。基本上,如果i+1 在数组中,我们将索引为i 的元素的值更改为负数。

Step 2 Array changes to: -1 -8 -2 -4 3 | -3 -5 -1

在第 3 步中,如果某个值 array[index] 是正数,则意味着我们在第 2 步中没有找到值 index + 1 的任何整数。

Step 3: Traversing from index 0 to end, we find array[4] = 3 > 0
        The answer is 4 + 1 = 5

【讨论】:

  • 你能举个例子解释一下吗?
  • 解决方案效果很好。如果我没记错的话,这是问题的扩展,说明在相同的时间和空间限制下查找数组中的重复项。很好解释。非常感谢
  • 上述解决方案未处理的一种情况是数组包含所有非正整数。在这种情况下,答案应该是 1。
  • @yosemite_k 因为它可能已经被切换并且是负数。例如,如果您有输入数组[2,3],第一步将找到2 并切换索引2-1=1 处的数字,这将导致数组[2,-3]。但是,在下一步中,您需要使用 3 而不是 -3,因此是绝对值。
  • @Kaushal28 在步骤 2.2 中,我们不切换。一旦符号为负,它仍然是负的。对于 [1,1],对于第一个 1 变为负数的第 0 个索引对于第二个 1 仍然为负数。“使元素的符号为负数”意味着符号保持负数,并说明两种情况 1)当它是正数时和 2) 当它已经是负数时(因为元素已经是负数,所以使符号为负数没有效果)。我完全理解,在使用上面的代码编写代码时,措辞会导致一些误解,所以我会添加一个说明。
【解决方案2】:

这实际上是一个LeetCode problem,它要求O(n) 时间和O(1) 空间,因此对输入进行排序或将其转换为集合是行不通的。 这是pmcarpan's answer 的 Python 3 实现,它在O(n) 时间运行并使用O(1) 空间。

def missing_int(nums: MutableSequence[int]) -> int:
    # If empty array or doesn't have 1, return 1
    if not next((x for x in nums if x == 1), 0):
        return 1

    lo: int = 0
    hi: int = len(nums) - 1
    i: int = 0
    pivot: int = 1

    while i <= hi:
        if nums[i] < pivot:
            swap(nums, i, hi)
            hi -= 1
        elif nums[i] > pivot:
            swap(nums, i, lo)
            i += 1
            lo += 1
        else:
            i += 1

    x = 0
    while x <= hi:  # hi is the index of the last positive number
        y: int = abs(nums[x])
        if 0 < y <= hi + 1 and nums[y - 1] > 0:  # Don't flip sign if already negative
            nums[y - 1] *= -1
        x += 1

    return next((i for i, v in enumerate(nums[:hi + 1]) if v >= 0), x) + 1

测试:

def test_missing_int(self):
    assert func.missing_int([1, 2, 1, 0]) == 3
    assert func.missing_int([3, 4, -1, 1]) == 2
    assert func.missing_int([7, 8, 9, 11, 12]) == 1
    assert func.missing_int([1]) == 2
    assert func.missing_int([]) == 1
    assert func.missing_int([0]) == 1
    assert func.missing_int([2, 1]) == 3
    assert func.missing_int([-1, -2, -3]) == 1
    assert func.missing_int([1, 1]) == 2
    assert func.missing_int([1000, -1]) == 1
    assert func.missing_int([-10, -3, -100, -1000, -239, 1]) == 2
    assert func.missing_int([1, 1]) == 2

【讨论】:

  • 还有测试。
  • 没有测试的代码是糟糕的代码。写得有多好并不重要;不管它多么漂亮、面向对象或封装得多么好。通过测试,我们可以快速且可验证地更改代码的行为。没有它们,我们真的不知道我们的代码是变得更好还是更糟。” - Michael Feathers,有效地使用遗留代码。
【解决方案3】:

PMCarpan 的算法有效。

我认为您的方法有效,但您应该指定您正在执行的排序类型,以便清楚它是线性排序,而不一定是整个数组的完整排序。这导致 O(N) 时间而不使用任何空间。

扫描数组,当您正在扫描时,如果当前索引中的值小于数组的长度,则将其与该索引中当前的值交换。您必须继续交换,直到在每个索引处交换不再有意义。然后最后再扫描一次,直到找到不正确的索引。

这里有一些可用的 python 代码,虽然 python 不是做这种事情的地方,哈哈。

def sortOfSort(arr) :
    for index in range(len(arr)) :
        checkValue = arr[index]

        while(checkValue > 0 and checkValue != index and checkValue < len(arr) and arr[checkValue] != checkValue) :
            arr[index] = arr[checkValue]
            arr[checkValue] = checkValue
            checkValue = arr[index]

    return arr[1:] + [arr[0]]

def findFirstMissingNumber(arr) :
    for x in range(len(arr)) :
        if (x+1 != arr[x]) :
            return x+1
    return len(arr) + 1

返回 arr[1:] 部分是因为根据您的描述,我们没有将零作为起点。

【讨论】:

  • 你用 [3, 2, 1, 6, 5, 4] 测试过吗?
  • @yosemite_k 这个问题是在 2018 年提出的,我不知道我是否对此进行了测试,或者我什至根本没有对其进行测试,但我刚刚对其进行了测试,它产生了“7 " 我相信这是正确的答案。
  • 对我来说它产生了“1”
  • 有两个函数,你必须运行第一个函数作为第二个函数的输入。 EG findFirstMissingNumber(sortOfSort([3, 2, 1, 6, 5, 4]))
  • 我明白了,那最好修改代码在findFirstMissing function里面调用sortOfSort
【解决方案4】:

这是一个 C 实现
输入

#include <stdio.h>
#include <stdlib.h>
//Here we separate the positive and negative number
int separate (int arr[], int size)
{
    int j = 0, i , temp;
    for(i = 0; i < size; i++)
    {
    if (arr[i] <= 0)
    {
        /*Here we using bitwise operator to swap the
        numbers instead of using the temp variable*/
         arr[j] = arr[j]^arr[i];
         arr[i] = arr[j]^arr[i];
         arr[j] = arr[j]^arr[i];
         j++;
    }
    }
    printf("First We Separate the negetive and positive number \n");
    for( i = 0 ; i <size ; i++)
    {
        printf("Array[%d] = %d\n",i,arr[i]);
    }
    return j;
}
int findMissingPositive(int arr[], int size)
{
printf("Remove the negative numbers from array\n");
int i;
for( i = 0 ; i <size ; i++)
{
        printf("Array[%d] = %d\n",i,arr[i]);
}
for(i = 0; i < size; i++)
{
    if(abs(arr[i]) - 1 < size && arr[ abs(arr[i]) - 1] > 0)
    arr[ abs(arr[i]) - 1] = -arr[ abs(arr[i]) - 1];
}
for(i = 0; i < size; i++)
    if (arr[i] > 0)
    {
    return i+1;
    }
return size+1;
}
int findMissing(int arr[], int size)
{
int j = separate (arr, size);
return findMissingPositive(arr+j, size-j);
}
int main()
{
int size ;
printf("Enter the Value of Size of Array : ");
scanf("%d",&size);
int arr[size];
printf("Enter the values :\n");
for( int i = 0 ; i < size ; i++)
{
    printf("Array[%d] = ",i);
    scanf("%d",&arr[i]);
}
int missing = findMissing(arr,size);
printf("The smallest positive missing number is %d ", missing);
return 0;
}


输出
Enter the Value of Size of Array : 8
Enter the values :
Array[0] = 1
Array[1] = -1
Array[2] = -5
Array[3] = -3
Array[4] = 3
Array[5] = 4
Array[6] = 2
Array[7] = 8
First We Separate the negetive and positive number
Array[0] = -1
Array[1] = -5
Array[2] = -3
Array[3] = 1
Array[4] = 3
Array[5] = 4
Array[6] = 2
Array[7] = 8
Remove the negative numbers from array
Array[0] = 1
Array[1] = 3
Array[2] = 4
Array[3] = 2
Array[4] = 8
The smallest positive missing number is 5
Process returned 0 (0x0)   execution time : 27.914 s
Press any key to continue.

 /*
        How work :
        [if(abs(arr[i]) - 1 < size && arr[ abs(arr[i]) - 1] > 0)
        arr[ abs(arr[i]) - 1] = -arr[ abs(arr[i]) - 1];]
        before: arr = { 7, 3, 4, 5, 5, 3, 2}
    i == 0: arr[0] = 7
            arr[7-1] is 2 > 0 ~> negate
            arr = { 7, 3, 4, 5, 5, 3, -2}
    i == 1: arr[1] = 3
            arr[3-1] is 4 > 0 ~> negate
            arr = { 7, 3, -4, 5, 5, 3, -2}
    i == 2: arr[2] is -4 ~> abs for indexing
            arr[4-1] is 5 > 0 ~> negate
            arr = { 7, 3, -4,-5, 5, 3, -2}
    i == 3: arr[3] is -5 ~> abs for indexing
            arr[5-1] is 5 > 0 ~> negate
            arr = { 7, 3, -4, -5, -5, 3, -2}
    i == 4: arr[4] is -5 ~> abs for indexing
            arr[5-1] is -5 < 0 ~> print abs(-5) as duplicate
    i == 5: arr[5] is 3
            arr[3-1] is -4 < 0 ~> print abs(3) as duplicate
    i == 6: arr[6] is -2 ~> abs for indexing
            arr[2-1] is 3 > 0 ~> negate
            arr = { 7, -3, -4, -5, -5, 3, -2}

            indices of positive entries: 0, 5 ~> 1 and 6 not in original array
            indices of negative entries: 1, 2, 3, 4, 6 ~> 2, 3, 4, 5, 7 in original array
*/

【讨论】:

  • 如果将负值移动到右边而不是左边会更容易:#include int main (void) { int data[] = { -9, 7, 5, 0, 6, 2000, 1, 3, -1, 1, 9, -9 }; int i=0, size = sizeof(data)/sizeof(*data); do { while ((data[i] 0))数据[数据[i]-1] *= -1;尺寸+=1; for (i = 1; i 0) {size = i; break;} printf ("%d\n", size); fflush(标准输出);返回(0); }
【解决方案5】:
#Returns a slice containing positive numbers
def findPositiveSubArr(arr):
    negativeIndex = 0

    if i in range(len(arr)):
        if arr[i] <=0:
            arr.insert(negativeIndex, arr.pop(i))
            negativeIndex += 1
    return arr[negativeIndex:]

#Returns the first missing positive number
def findMissingPositive(positiveArr):
    l = len(positiveArr)
    for num in positiveArr:
        index = abs(num) - 1
        if index < 1 and positiveArr[index] > 0:
            positiveArr[index] *= -1

    for i in range(l):
        if positiveArr[i] > 0:
            return i+1

    return l+1

if __name__ == "__main__":
    arr = [int(x) for x in input().strip().split()]
    positiveSubArr = findPositveSubArr(arr)
    print(findMissingPositive(positiveSubArr))

【讨论】:

    【解决方案6】:

    我在 python3 中使用 set 解决了这个问题。这是非常简单的6LOC。 时间复杂度:O(n)。

    记住:集合中的成员资格检查是 O(1)

    def first_missing_positive_integer(arr):
        arr = set(arr)
        for i in range(1, len(arr)+2):
            if i not in arr:
                return i
    

    【讨论】:

    • 空间复杂度如何?您需要额外的空间来放置套装。
    • 但是您也可以在 O(n) 中搜索 arr,但结果是 O(n^2)。 range(1,len(arr)+2) 应该是常量空间,因为它是从 range 对象懒惰地评估。
    • @fanfabbb,额外的空间取决于你的实现。但是时间复杂度是 O(n),因为集合中的成员资格检查是 O(1),而 for 循环复杂度是 O(n)(数组与 set() 相同)。因此整体渐近时间复杂度为 O(n)。
    • 我问的是你的实现的空间复杂度(6LOC)?它是否按要求保持不变?
    【解决方案7】:

    这要简单得多。 (解决方案不是我的)

    public static int Missing(int[] a)
    {
        // the idea is to put all values in array on their ordered place if possible
        for (int i = 0; i < a.Length; i++)
        {
            CheckArrayAtPosition(a, i);
        }
    
        for (int i = 0; i < a.Length; i++)
            if (a[i] != i + 1)
                return i + 1;
        return a.Length + 1;
    }
    
    private static void CheckArrayAtPosition(int[] a, int i)
    {
        var currentValue = a[i];
        if (currentValue < 1) return; // do not touch negative values because array indexes are non-negative
        if (currentValue > a.Length) return; // do not touch values that are bigger than array length because we will not locate them anyway
        if (a[currentValue - 1] == currentValue) return; // do not need to change anything because index contain correct value already
        Swap(a, i, currentValue - 1);
        CheckArrayAtPosition(a, i); // now current position value is updated so we need to check current position again
    }
    
    private static void Swap(int[] a, int i, int j)
    {
        int temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
    

    存在递归,但由于每个交换将 1 个值放在正确的位置,因此将有

    【讨论】:

      【解决方案8】:

      这是另一个具有 o(n) 时间复杂度和 o(1) 空间复杂度的 python 实现

      def segregate(arr):
          length = len(arr)
          neg_index = length
          for i, value in enumerate(arr):
              if(value < 1 and neg_index == length):
                 neg_index = i
              if(neg_index != length and value >= 1):
                 temp = arr[i]
                 arr[i] = arr[neg_index]
                 arr[neg_index] = temp
                 neg_index += 1
          return arr[:neg_index]
      
      def missingPositiveNumber(arr):
          arr = segregate(arr)
          length = len(arr)
          for i, value in enumerate(arr):
              if(value - 1 < l):
                 arr[abs(value) - 1] = -(abs(arr[abs(value) - 1]))
          for i, value in enumerate(arr):
              if(value > 0):
                 return i + 1
          return length + 1
      
      
      print(missingPositiveNumber([1, -1, 2, 3]))
      

      【讨论】:

        【解决方案9】:

        这是在 Java 中。时间复杂度 f O(N) 和空间复杂度 O(1)

        private static int minimum_positive_integer(int[] arr) {
                int i = 0;
                int j = arr.length - 1;
        
                //splitting array
                while (i < j) {
                    if (arr[i] > 0) {
                        i++;
                    }
        
                    if (arr[j] <= 0) {
                        j--;
                    }
        
                    if (arr[i] <= 0 && arr[j] > 0) {
                        int t = arr[i];
                        arr[i] = arr[j];
                        arr[j] = t;
        
                        i++;
                        j--;
                    }
                }
                int len_positive = i;
        
                if (arr[i] > 0) len_positive++;
        
                for (i = 0; i < len_positive; i++) {
                    int abs = Math.abs(arr[i]);
                    if (abs <= len_positive) {
                        int index = abs - 1;
                        arr[index] = -abs;
                    }
                }
        
                for (i = 0; i < len_positive; i++) {
                    if(arr[i] > 0) return  i + 1;
                }
        
                return len_positive + 1;
            }
        

        【讨论】:

          【解决方案10】:

          我在 Python 中的解决方案:

          def lowest_positive(lista):
          
          result = 0
          dict = {}
          
          for i in lista:
          
              if i <= 0:
                  continue
          
              if i in dict:
                  continue
              else:
                  dict[i] = i
          
                  if result == 0:
                      result = result +1
          
                  if result < i: 
                      continue
          
                  result = result +1
          
                  while result in dict:
                      result = result +1
          
          return result
          

          测试用例:

          lista = [5, 3, 4, -1, 1, 2]
          lista = [1,2,3,4,5]
          lista = [3, 4, -1, 1]
          lista = [2, 3, 4, 1]
          lista = [1,0]
          lowest_positive(lista)
          

          请注意,我不认为 0 是正数

          逻辑:如果数字小于0,则拒绝。然后在字典中检查该数字是否存在,如果存在,则读取下一个数字,否则将其添加到字典中。 result 是一个一个递增的计数器。如果结果小于列表中读取的数字,则读取下一个数字,否则,计数器加一并且该结果也在字典中检查。全部字典将存储列表中读取的所有数字,以及列表中读取的最小数字之间的任何缺少的正数。

          【讨论】:

            【解决方案11】:

            上述方法的缺点是分配“max_value”需要额外的空间,这不是正确的解决方案

            def missing_positive_integer(my_list):
                max_value = max(my_list)
                my_list = [num for num in range(1,max(my_list)) if num not in my_list]
                if len(my_list) == 0:
                    my_list.append(max_value+1)
            
                return min(my_list)
            
            my_list = [1,2,3,4,5,8,-1,-12,-3,-4,-8]
            missing_positive_integer(my_list)
            

            【讨论】:

              【解决方案12】:

              JavaScript:

              let findFirstMissingNumber = ( arr ) => {
                    // Sort array and find the index of the lowest positive element.
                    let sortedArr = arr.sort( (a,b) => a-b );
                    const lowestPositiveIndex = arr.findIndex( (element) => element > 0 );
              
                    // Starting from the lowest positive element
                    // check upwards if we have the next integer in the array.
                    let i = lowestPositiveIndex;
                    while( i < sortedArr.length ) {
                      if ( sortedArr[ i + 1 ] !== sortedArr[ i ] + 1 ) {
                        return sortedArr[ i ] + 1
                      } else {
                        i += 1;
                      }
                    }
                  }
              
                  console.log( findFirstMissingNumber( [3, 4, -1, 1, 1] ) ); // should give 2
                  console.log( findFirstMissingNumber( [0, 1, 2, 0] ) ); // should give 3

              【讨论】:

                【解决方案13】:

                PMCarpan算法的Javascript实现

                function getMissingInt(array) {
                  const segArray = array.filter((a) => a > 0);
                  for (let i = 0; i < segArray.length; i++) {
                    const value = Math.abs(segArray[i]);
                    if (value <= segArray.length && segArray[value - 1] > 0) {
                      segArray[value - 1] *= -1;
                    }
                  }
                  for (let i = 0; i < segArray.length; i++) {
                    if (segArray[i] > 0) {
                      return i + 1;
                    }
                  }
                  return segArray.length + 1;
                }
                console.log(getMissingInt([1, -1, -5, -3, 3, 4, 2, 8]));
                console.log(getMissingInt([3, 4, -1, 1]));
                console.log(getMissingInt([1, 2, 0]));

                【讨论】:

                  【解决方案14】:

                  我没有对其进行详细测试,但对于排序数组,我将采用以下方法,欢迎任何改进。 约束:

                  • 线性时间
                  • 固定空间

                    solution:
                    start with lowest positive integer (i.e. lpi <- 1)
                    while parsing the array, if lpi is already in the array, increment it
                    

                  lpi 现在是数组中不可用的最小正整数

                  简单的python函数如下:

                  def find_lpi(arr):
                      lpi = 1
                      for i in arr:
                          if lpi == i:
                              lpi += 1
                      return lpi
                  

                  如果数组未排序,以下可能是替代解决方案。

                  首先创建一个长度为 max(arr) 的零二进制数组 X。 对于数组中的每个项目,将 X 的索引标记为 1 返回 0 的最小索引

                  以下是满足的简单实现

                  • 线性时间
                  • 恒定空间复杂度约束。

                    def find_lpi(arr):
                        x = [0 for x in range(max(arr)+1)]
                         for i in arr:
                             x[i] = 1 
                         for i in range(1,len(x)):
                             if x[i] ==0:
                                 return i
                         return len(x)
                    

                  【讨论】:

                  • 并不是说数组是有序的。如果是,您可以简单地循环比较两个相邻元素,如果它们不是 N 和 N+1,则在此之间缺少某些内容。
                  • 对于未排序的数组,它需要更多的思考和更多的变量。
                  【解决方案15】:
                  public int FindMissing(){
                      var list = new int[] { 6, -6, 4, 5 };
                      list = list.OrderBy(x => x).ToArray();
                      var maxValue = 0;
                      for (int i = 0; i < list.Length; i++)
                      {
                          if (list[i] <= 0)
                          {
                              continue;
                          }
                          if (i == list.Length - 1 ||
                              list[i] + 1 != list[i + 1])
                          {
                              maxValue = list[i] + 1;
                              break;
                          }
                      }
                      return maxValue;
                  }
                  
                  1. 按升序对数据进行排序:
                  2. for 循环数据
                    • 如果值小于等于 0,则不执行任何操作并跳过。
                    • 检查当前索引值加1是否等于下一个索引值
                      • 如果是,继续循环。
                      • 如果否,当前索引值加 1 将是缺失的正整数

                  【讨论】:

                  • 排序在 n(数组的元素数)中不是线性的
                  猜你喜欢
                  • 1970-01-01
                  • 2018-09-10
                  • 2021-07-17
                  • 1970-01-01
                  • 2014-03-06
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多