簡體   English   中英

"在 Python 中查找第一個丟失的正整數"

[英]Find the first missing positive integer in Python

我有以下任務:

給定一個未排序的整數數組,找到第一個丟失的正整數。 您的算法應該在 O(n) 時間內運行並使用恆定空間。

想了想得到了提示,我決定把輸入列表A改一下。下面是代碼:

當我運行它時,它需要很長時間。 我該怎么做才能讓它更快一點?

編輯:這是列表 A。

在我看來,在 O(n) 和恆定空間中都這樣做是不可能的。 (我更正了, Rockybilly的鏈接給出了這樣的解決方案)

要在恆定空間中進行排序,一種是強制對列表進行排序,對於大多數排序算法來說,這是 O(n log n),而這里的那些看起來像插入排序,平均 O(n 2 )

因此,FWIW 我選擇丟棄常量空間,以便盡可能接近 O(n),這給了我 2n 解決方案(在最壞的情況下,平均為 n+k)(這在大O 符號仍然是 O(n) )

def firstMissingSince(sequence, start=1):
    uniques = set()
    maxitem = start-1
    for e in sequence:
        if e >= start:
            uniques.add(e)
            if e > maxitem:
                maxitem = e
    return next( x for x in range(start, maxitem+2) if x not in uniques )

(版本 2 波紋管)

我可以使用set(sequence)max(sequence)但兩者都是 O(n),所以我將它們組合在同一個循環中,我使用set有兩個原因:首先是通過忽略重復項和以同樣的方式,我只關心大於或等於我的下限(我也將其設為通用)的數字,第二個是 O(1) 成員資格測試。

最后一行是對缺失元素的簡單線性搜索,其屬性默認為如果最大元素低於開始時開始,或者如果數組在開始和最大值之間沒有缺失元素則為最大值+1。

這里有一些測試借鑒了其他答案......

assert 1 == firstMissingSince(A) 
assert 2 == firstMissingSince([1,4,3,6,5])
assert 2 == firstMissingSince([1,44,3,66,55]) 
assert 6 == firstMissingSince([1,2,3,4,5]) 
assert 4 == firstMissingSince([-6, 3, 10, 14, 17, 6, 14, 1, -5, -8, 8, 15, 17, -10, 2, 7, 11, 2, 7, 11])
assert 4 == firstMissingSince([18, 2, 13, 3, 3, 0, 14, 1, 18, 12, 6, -1, -3, 15, 11, 13, -8, 7, -8, -7])
assert 4 == firstMissingSince([-6, 3, 10, 14, 17, 6, 14, 1, -5, -8, 8, 15, 17, -10, 2, 7, 11, 2, 7, 11])
assert 3 == firstMissingSince([7, -7, 19, 6, -3, -6, 1, -8, -1, 19, -8, 2, 4, 19, 5, 6, 6, 18, 8, 17])

Rockybilly的回答讓我意識到我根本不需要知道最大值,所以這里是第 2 版

from itertools import count

def firstMissingSince(sequence, start=1):
    uniques = set(sequence) # { x for x in sequence if x>=start } 
    return next( x for x in count(start) if x not in uniques )

代碼

def first_missing_positive(nums):
    bit = 0
    for n in nums:
        if n > 0:
            bit |= 1 << (n - 1)
    flag = 0
    while bit != 0:
        if (bit & 1 == 0):
            break
        flag += 1
        bit >>= 1
    return flag + 1

解釋

通常,對於恆定的空間要求,按位解決方案很好。

這里的技巧是傳遞所有整數並將它們的二進制表示形式存儲在單個變量中。 說“一點”。 例如,當nums = [1, 2, 3]nums_bitwise = [1, 10, 11]bit = "11" 11這里表示序列[1, 10, 11]在一個簡明的形式。

現在,讓我們假設nums中缺少2 然后我們有nums = [1, 3]nums_bitwise = [1, 11] , bit = "101" 我們現在可以遍歷“bit”變量的所有位以找出第一個缺失的正整數2即“101”中的“0”

請注意,對於nums=[1, 3]nums=[1, 2, 3] ,bit 的值將分別為57 您需要為其二進制表示做"{0:b}".format(bit)

關鍵線

bit |= 1 << (n - 1)

通過向左移動、逐位移動以及將整數的bitbit變量的默認 0 進行 OR 運算,將所有整數存儲在nums

接下來,我們做

if (bit & 1 == 0):
    break

在壓縮bit變量中找到第一個零。 第一個零表示第一個缺失的整數。 右移bit >>= 1並查找該位是否丟失。 如果不是,則繼續將bit變量的最后一位與1 AND 運算,直到結果為0

分析

由於我們只查看nums中的每個整數一次,因此時間復雜度為O(n) 假設所有整數都可以壓縮在一個變量中,空間復雜度為O(1)即常數空間。

可能不是長時間運行的完整原因,但我確實發現了一個會導致無限循環的錯誤。 我首先創建長度為 20 的隨機整數數組。

a = [random.randint(-10, 20) for _ in range(20)]

添加了兩個打印語句以查看發生了什么。

    while i<ln:
        print(A)
        if A[i]>=1 and A[i]<=ln:
            if A[A[i]-1]!=m+1:
                print("Swapping %d"%i)
                A[A[i]-1], A[i] = m+1, A[A[i]-1]
            else:
       ...

將此數組作為輸入,您將進入無限循環:

a = [-6, 3, 10, 14, 17, 6, 14, 1, -5, -8, 8, 15, 17, -10, 2, 7, 11, 2, 7, 11]

>>>
...
[18, 18, -8, -10, -6, 6, 14, 18, -5, 18, 18, 15, 17, 18, 2, 7, 18, 18, 7, 11]
Swapping 5
[18, 18, -8, -10, -6, 6, 14, 18, -5, 18, 18, 15, 17, 18, 2, 7, 18, 18, 7, 11]
Swapping 5
[18, 18, -8, -10, -6, 6, 14, 18, -5, 18, 18, 15, 17, 18, 2, 7, 18, 18, 7, 11]
...

事實證明,如果A[A[i]-1]等於A[i]那么你最終總是把相同的數字放回A[i] 在這種情況下i == 5A[5] == 6A[A[i]-1] == 6 在這份聲明中,

A[A[i]-1], A[i] = m+1, A[A[i]-1]

評估右手邊; m+1分配給A[5] 然后 6 分配給A[5] 我通過交換分配順序為這種情況修復了它:

A[i], A[A[i]-1] = A[A[i]-1], m+1

使用您添加到問題中的列表,它現在會使用我的 mod 拋出一個 IndexError。 即使右手邊首先被評估,似乎在左手邊的A[A[i]-1]直到第一次賦值之后才會被評估,並且在A[i]放置了大量數字A[i]

抄襲Rob 的解決方案- 在進行任何交換之前評估[A[i]-1

def firstMissingPositive(A):
    m=max(A)
    ln=len(A)
    print('max:{}, len:{}'.format(m, ln))
    i=0
    while i<ln:
##        print(A[:20])
        if A[i]>=1 and A[i]<=ln:
            if A[A[i]-1]!=m+1:
##                print("Swapping %d"%i)
                v = A[i]-1
                A[i], A[v] = A[v], m+1
            else:
                i+=1
        else:
            i+=1
    for i in range(ln):
        if A[i]!=m+1:
            return i+1

它有時仍然會返回錯誤的結果,所以對我來說減去一個。 它會產生以下錯誤結果:

[18, 2, 13, 3, 3, 0, 14, 1, 18, 12, 6, -1, -3, 15, 11, 13, -8, 7, -8, -7]
[-6, 3, 10, 14, 17, 6, 14, 1, -5, -8, 8, 15, 17, -10, 2, 7, 11, 2, 7, 11]
[7, -7, 19, 6, -3, -6, 1, -8, -1, 19, -8, 2, 4, 19, 5, 6, 6, 18, 8, 17]

FWIW,這是我的做法:

def firstMissingPositive(A):
    for i in range(len(A)):
        while A[i] != i+1 and 0 < A[i] < len(A):
            value = A[i]-1
            A[i], A[value] = A[value], A[i]
    for i, value in enumerate(A, 1):
        if i != value:
            return i
    return len(A)+1

assert firstMissingPositive([1,4,3,6,5]) == 2
assert firstMissingPositive([1,44,3,66,55]) == 2
assert firstMissingPositive([1,2,3,4,5]) == 6
assert firstMissingPositive(A) == 1
def firstMissingPositve(nums):
    if nums == []:
        return 1
    else:
        a = max(nums)
        for i in range(1 , a+2):
            if i not in nums:
                c = i
                return c
def missingNumber(arr, n):
    x = {i for i in arr if i > 0}
    b = max(arr)
    for i in range(1, b + 2):
        if i not in x:
            return i

我的python代碼如下。 O(n) 時間和 O(1) 空間復雜度。 受到@pmcarpan 在此相關問題中的高級解決方案的啟發。 還可以在我的 github 上查看完整評論的降價版本

def lowestMissingStrictlyPositiveInteger(arr):
    """ Return the lowest missing strictly 
    positive integer from the array arr. 
    Warning: In order to achieve this in linear time and
    constant space, arr is modified in-place.
    
    Uses separatePositiveIntegers() to isolate all
    strictly positive integers, and marks their occurrence
    with markIndicesOfObservedPositiveIntegers(). This 
    function then scans the modified array for the 'marks'
    and returns the first unmarked value. """
    m = separatePositiveIntegers(arr)
    markIndicesOfObservedPositiveIntegers(arr, m)
    for i in range(m): #O(m)
        if arr[i]>0:
            # this index hasn't been marked by
            # markIndexOfObservedPositiveIntegers(), 
            # therefore the integer i+1 is missing.
            return i+1
    return m+1

def separatePositiveIntegers(arr):
    """ Modify input array in place, so that 
    strictly positive integers are
    all at the start of the array, 
    and negative integers are
    all at the end of the array. 
    
    Return the index of the first negative 
    integer in the updated array (or len(arr)
    if all values are positive). """
    i1, i2 = 0, len(arr)-1
    while (i2 > i1): #O(n)
        
        if arr[i2]<=0:
            # move to the first strictly positive value
            # starting from the end of the array.
            i2 -= 1
            continue
        
        if arr[i1]>0:
            # move to the first negative value
            # from the start of the array.
            i1 += 1
            continue
        
        # swap negative value at i1 with the first
        # strictly positive value starting from the
        # end of the array (i.e., at position i2).
        tmp = arr[i2]
        arr[i2] = arr[i1]
        arr[i1] = tmp
    
    return i1 if arr[i1]<=0 else i1+1

def markIndicesOfObservedPositiveIntegers(arr, m):
    """ Take an array arr of integer values, 
    where indices [0,m-1] are all strictly positive
    and indices >= m are all negative
    (see separatePositiveIntegers() method).
    
    Mark the occurrence of a strictly positive integer
    k<=m by assigning a negative sign to the value in 
    the array at index k-1 (modify in place)."""
    for i in range(m): #O(m)
        # all values at indices [0,m-1] are strictly positive 
        # to start with, but may have been  modified in-place 
        # (switched to negative sign) in this loop. 
        # Therefore, read the untampered value as abs(arr[i]).
        untampered_val=abs(arr[i])
        # We can safely ignore any untampered value strictly superior to m
        # because it guarantees a gap in the integer sequence at a lower value 
        # (since arr only has m strictly positive integers).
        if untampered_val<=m:
            # mark the integer as "seen" by
            # changing the sign of the value at
            # index untampered_val-1 to negative.
            arr[untampered_val-1] = -abs(arr[untampered_val-1])

# test 1
arr = [3, 4, -1, 1]
assert lowestMissingStrictlyPositiveInteger(arr) == 2

# test 2
arr = [2, 0, 1]
assert lowestMissingStrictlyPositiveInteger(arr) == 3

# test 3
arr = [0]
assert lowestMissingStrictlyPositiveInteger(arr) == 1

這是一個更好的答案不要介意長代碼不符合我們需要質量而不是短代碼

    def firstMissingPositive(self, nums):
        if 1 not in nums:
            return 1
        n = len(nums)
        for i in range(n):
            if nums[i] > n or nums[i] <= 0:
                nums[i] = 1
        for i in range(n):
            a = abs(nums[i])
            if a == n:
                nums[0] = -abs(nums[0])
            else:
                nums[a] = -abs(nums[a])
        for i in range(2, n):
            if nums[i] > 0: return i
        return n if nums[0] > 0 else n+1

試試這個:

def firstMissingPositive(A):
        try:
            return min(set(range(1, len(A)+1)) - set(A))
        except:
            return max(1, max(A)+1)

這是我的解決方案。

from collections import defaultdict

def firstMissingPositive(A):

    d = defaultdict(int)
    for i in A:
        d[i] = 1

    j = 1
    while True:
        if d[j] == 0:
            return j
        j += 1

空間復雜度: O(n)

時間復雜度: O(n + k)。 這被認為是 O(n)。 也忽略了哈希復雜性。


順便說一句: 谷歌搜索給出了你尋求的答案,恆定的空間和 O(n) 時間。

  • 復雜度:O(N) 或 O(N * log(N))
  • 如果您想測試親切感,您將獲得100%的分數
  • 背后的想法是使用 1 個循環和一次字典來避免 O(N^2) 復雜性
  • 問題中提到的列表所花費的時間:0:00:00.000098
def test(A):
    value = 1
    data = {}
    for num in A:
        if num < 0:
            continue
        elif num > 0 and num != value:
            data[num] = 1
            continue
        elif num == value:
            data[num] = 1
            while data.get(value, None) is not None:
                value += 1
    return value

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM