簡體   English   中英

找到不在列表中的最小正數

[英]Find the smallest positive number not in list

我在 python 中有一個這樣的列表:

myList = [1,14,2,5,3,7,8,12]

如何輕松找到第一個未使用的值? (在本例中為“4”)

我想出了幾種不同的方法:

迭代第一個不在集合中的數字

我不想得到最短的代碼(這可能是 set-difference 詭計),而是一些可能有良好運行時間的東西。

這可能是這里提出的最好的方法之一,我的測試表明它可能比 set-difference 方法快得多 - 特別是如果洞在開始時 - :

from itertools import count, filterfalse # ifilterfalse on py2

A = [1,14,2,5,3,7,8,12]
print(next(filterfalse(set(A).__contains__, count(1))))

該數組變成了一個set ,其__contains__(x)方法對應x in A count(1)創建一個從 1 開始計數到無窮大的計數器。 現在, filterfalse使用計數器中的數字,直到找到不在集合中的數字; 當找到不在集合中的第一個數字時,它由next()

len(a) = 100000 ,隨機且搶手的數字是8

>>> timeit(lambda: next(filterfalse(set(a).__contains__, count(1))), number=100)
0.9200698399945395
>>> timeit(lambda: min(set(range(1, len(a) + 2)) - set(a)), number=100)
3.1420603669976117

len(a) = 100000 ,已訂購,第一個空閑的是100001

>>> timeit(lambda: next(filterfalse(set(a).__contains__, count(1))), number=100)
1.520096342996112
>>> timeit(lambda: min(set(range(1, len(a) + 2)) - set(a)), number=100)
1.987783643999137

(請注意,這是 Python 3, range是 py2 xrange

使用 heapq

漸近好的答案: heapq with enumerate

from heapq import heapify, heappop

heap = list(A)
heapify(heap)

from heapq import heapify, heappop
from functools import partial

# A = [1,2,3] also works
A = [1,14,2,5,3,7,8,12]

end = 2 ** 61      # these are different and neither of them can be the 
sentinel = 2 ** 62 # first gap (unless you have 2^64 bytes of memory).

heap = list(A)
heap.append(end)
heapify(heap)

print(next(n for n, v in enumerate(
     iter(partial(heappop, heap), sentinel), 1) if n != v))

現在,如果用 C 編寫,上面的可能是首選解決方案,但heapq是用 Python 編寫的,很可能比其他許多主要使用 C 代碼的替代方案慢。

只需排序和枚舉即可找到第一個不匹配的

或者 O(n lg n) 具有良好常數的簡單答案

next(i for i, e in enumerate(sorted(A) + [ None ], 1) if i != e)

如果由於 Python Timsort 的工作方式而幾乎對列表進行了排序,那么這可能是最快的,但是對於隨機化的集合差異和迭代第一個不在集合中的集合會更快。

+ [ None ]對於沒有間隙的邊緣情況(例如[1,2,3] )是必要的。

這利用了集合的屬性

>>> l = [1,2,3,5,7,8,12,14]
>>> m = range(1,len(l))
>>> min(set(m)-set(l))
4

我建議您使用生成器並使用 enumerate 來確定缺少的元素

>>> next(a for a, b in enumerate(myList, myList[0]) if a != b)
4

enumerate將索引與元素映射在一起,因此您的目標是確定與其索引不同的元素。 請注意,我還假設元素可能不會以確定的值開頭,在這種情況下為1 ,如果是這樣,您可以進一步簡化表達式為

>>> next(a for a, b in enumerate(myList, 1) if a != b)
4

不知道效率如何,但為什么不使用 xrange 作為掩碼並使用 set減號?

>>> myList = [1,14,2,5,3,7,8,12]
>>> min(set(xrange(1, len(myList) + 1)) - set(myList))
4

您只創建了一個與myList一樣大的myList ,所以它不會那么糟糕:)

這不適用於“完整”列表:

>>> myList = range(1, 5)
>>> min(set(xrange(1, len(myList) + 1)) - set(myList))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: min() arg is an empty sequence

但是返回下一個值的修復很簡單(在掩碼集中再添加一個):

>>> min(set(xrange(1, len(myList) + 2)) - set(myList))
5
import itertools as it

next(i for i in it.count() if i not in mylist)

我喜歡這個,因為它與你想要做的事情非常接近:“開始計數,繼續計算,直到你找到一個不在列表中的數字,然后告訴我那個數字”。 然而,這是二次的,因為測試i not in mylist是線性的。

使用 enumerate 的解決方案是線性的,但依賴於正在排序的列表並且沒有重復的值。 先排序使其整體為 O(n log n),這仍然比二次方好。 但是,如果您可以假設這些值是不同的,那么您可以先將它們放入一個集合中:

myset = set(mylist)
next(i for i in it.count() if i not in myset)

由於設置包含檢查的時間大致恆定,因此總體上將是線性的。

帶有列表的 for 循環將執行此操作。

l = [1,14,2,5,3,7,8,12]
for i in range(1, max(l)):
    if i not in  l: break
print(i) # result 4

我只是以一種可能非 Pythonic 的方式解決了這個問題

def solution(A):
    # Const-ish to improve readability
    MIN = 1
    if not A: return MIN
    # Save re-computing MAX
    MAX = max(A)
    # Loop over all entries with minimum of 1 starting at 1
    for num in range(1, MAX):
        # going for greatest missing number return optimistically (minimum)
        # If order needs to switch, then use max as start and count backwards
        if num not in A: return num
    # In case the max is < 0 double wrap max with minimum return value
    return max(MIN, MAX+1)

我認為它讀得很好

我的努力,沒有itertools。 將“當前”設置為小於您期望的值。

list = [1,2,3,4,5,7,8]
current = list[0]-1
for i in list:
    if i != current+1:
        print current+1
        break
    current = i

最簡單的方法是遍歷 O(n) 解決方案的列表。 但是,由於列表已排序,因此您可以使用此功能執行二進制搜索(針對它的修改版本)。 基本上,您正在尋找 A[i] = i 的最后一次出現。

偽算法將類似於:

binarysearch(A):
  start = 0
  end = len(A) - 1
  while(start <= end ):
    mid = (start + end) / 2
    if(A[mid] == mid):
      result = A[mid]
      start = mid + 1
    else: #A[mid] > mid since there is no way A[mid] is less than mid
      end = mid - 1
  return (result + 1)

這是一個 O(log n) 解決方案。 我假設列表是一個索引。 您可以相應地修改索引

編輯:如果列表未排序,您可以使用 heapq python 庫並將列表存儲在最小堆中,然后將元素一一彈出

偽代碼

H = heapify(A) //Assuming A is the list
count = 1
for i in range(len(A)):
  if(H.pop() != count): return count
  count += 1

排序+減少來拯救!

from functools  import reduce # python3
myList = [1,14,2,5,3,7,8,12]
res = 1 + reduce(lambda x, y: x if y-x>1 else y, sorted(myList), 0)
print(res)

不幸的是,它不會在找到匹配項后停止,而是會迭代整個列表。

更快(但不那么有趣)是使用 for 循環:

myList = [1,14,2,5,3,7,8,12]
res = 0
for num in sorted(myList):
    if num - res > 1:
        break
    res = num
res = res + 1
print(res)

你可以試試這個

for i in range(1,max(arr1)+2):
        if i not in arr1:
            print(i)
            break

最簡單的方法是循環遍歷排序列表並檢查索引是否等於值,如果不等於將索引作為解決方案返回。 由於排序,這將具有復雜度 O(nlogn):

for index,value in enumerate(sorted(myList)):
        if index is not value:
            return index

另一種選擇是使用 python 集,它有點像沒有值的字典,只是鍵。 在字典中,您可以在恆定時間內尋找一個鍵,這使得整個解決方案如下所示,只有線性復雜度 O(n):

mySet = set(myList)
for i in range(len(mySet)):
    if i not in mySet:
        return i

在循環中不斷遞增計數器,直到找到第一個不在列表中的正整數。

def getSmallestIntNotInList(number_list):
    """Returns the smallest positive integer that is not in a given list"""
    i = 0
    while True:
        i += 1
        if i not in number_list:
            return i    

print(getSmallestIntNotInList([1,14,2,5,3,7,8,12]))
# 4

我發現與這篇文章中的其他答案相比,這具有最快的性能。 我在 Python 3.10.8 中使用timeit進行了測試。 我的性能結果如下所示:

import timeit

def findSmallestIntNotInList(number_list):
    # Infinite while-loop until first number is found
    i = 0
    while True:
        i += 1
        if i not in number_list:
            return i

t = timeit.Timer(lambda: findSmallestIntNotInList([1,14,2,5,3,7,8,12]))
print('Execution time:', t.timeit(100000), 'seconds')
# Execution time: 0.038100800011307 seconds
import timeit

def findSmallestIntNotInList(number_list):
    # Loop with a range to len(number_list)+1 
    for i in range (1, len(number_list)+1):
        if i not in number_list:
            return i

t = timeit.Timer(lambda: findSmallestIntNotInList([1,14,2,5,3,7,8,12]))
print('Execution time:', t.timeit(100000), 'seconds')
# Execution time: 0.05068870005197823 seconds
import timeit

def findSmallestIntNotInList(number_list):
    # Loop with a range to max(number_list) (by silgon)
    # https://stackoverflow.com/a/49649558/3357935
    for i in range (1, max(number_list)):
        if i not in number_list:
            return i

t = timeit.Timer(lambda: findSmallestIntNotInList([1,14,2,5,3,7,8,12]))
print('Execution time:', t.timeit(100000), 'seconds')
# Execution time: 0.06317249999847263 seconds
import timeit
from itertools import count, filterfalse

def findSmallestIntNotInList(number_list):
    # iterate the first number not in set (by Antti Haapala -- Слава Україні)
    # https://stackoverflow.com/a/28178803/3357935
    return(next(filterfalse(set(number_list).__contains__, count(1))))

t = timeit.Timer(lambda: findSmallestIntNotInList([1,14,2,5,3,7,8,12]))
print('Execution time:', t.timeit(100000), 'seconds')
# Execution time: 0.06515420007053763 seconds
import timeit

def findSmallestIntNotInList(number_list):
    # Use property of sets (by Bhargav Rao)
    # https://stackoverflow.com/a/28176962/3357935
    m = range(1, len(number_list))
    return min(set(m)-set(number_list))

t = timeit.Timer(lambda: findSmallestIntNotInList([1,14,2,5,3,7,8,12]))
print('Execution time:', t.timeit(100000), 'seconds')
# Execution time: 0.08586219989228994 seconds

返回所有這些值的解決方案是

free_values = set(range(1, max(L))) - set(L)

它會進行全面掃描,但這些循環是用 C 實現的,除非列表或其最大值很大,否則這將勝過在 Python 中執行循環的更復雜的算法。

請注意,如果需要此搜索來實現 ID 的“重用”,那么保留一個空閑列表並使其保持最新狀態(即在刪除條目時添加數字並在重用條目時從中挑選)通常是一個很好的選擇主意。

以下解決方案循環 1 和輸入列表長度之間的所有數字,並在其中找不到數字時中斷循環。 否則結果是列表的長度加一。

listOfNumbers=[1,14,2,5,3,7,8,12]
for i in range(1, len(listOfNumbers)+1):
   if not i in listOfNumbers: 
      nextNumber=i
      break
else:
   nextNumber=len(listOfNumbers)+1

易於閱讀,易於理解,完成工作:

def solution(A):
    smallest = 1
    unique = set(A)
    for int in unique:
        if int == smallest:
            smallest += 1
    return smallest

暫無
暫無

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

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