Бинарный поиск. Переполнение памяти и большое время выполнения программы
Дано два списка чисел, числа в первом списке упорядочены по неубыванию. Для каждого числа из второго списка определите номер первого и последнего появления этого числа в первом списке.
Входные данные В первой строке входных данных записано два числа N и M (1≤N,M≤20000 ). Во второй строке записано N упорядоченных по неубыванию целых чисел — элементы первого списка. В третьей строке записаны M целых неотрицательных чисел - элементы второго списка. Все числа в списках - целые 32-битные знаковые.
Выходные данные Программа должна вывести M строчек. Для каждого числа из второго списка нужно вывести номер его первого и последнего вхождения в первый список. Нумерация начинается с единицы. Если число не входит в первый список, нужно вывести одно число 0 .
Примеры Входные данные
10 5
1 1 3 3 5 7 9 18 18 57
57 3 9 1 179
Выходные данные
10 10
3 4
7 7
1 2
0
def binarnui_poisk (a,value):
low = 0
high = len(a) - 1
mid = len(a) // 2
while a[mid] != value and low <= high:
if value > a[mid]:
low = mid + 1
else:
high = mid - 1
mid = (low + high) // 2
if a[mid] != a[-1] and a.count(value) == 2:
if a[mid] == a[mid + 1] == value:
return mid+1,mid + 2
elif a[mid] == a[mid - 1] == value:
return mid, mid + 1
elif a[mid] == value:
return mid+1,mid+1
else:
return 0
N, K = map(int, input().split())
sp = [int(i) for i in input().split()]
sp1_ = [int(i) for i in input().split()]
for i in range(K):
k = binarnui_poisk(sp,sp1_[i])
if k == 0:
print(0)
else:
print(*k)
Из-за count() получается линейный алгоритм, а нужен логарифмический, поэтому не проходит.
Ответы (2 шт):
Была проблема а том что выполнялся линейный алгоритм, а надо было логарифмический, для того чтобы выполнялся логарифмический я использовал бинарный поиск, также я убрал метод count (), который из логарифмического делал линейный алгоритм
'''Код отредактированный'''
def left_binarnui_poisk (a,value): # левый бинарный поиск
result = -1
low = 0
high = len(a) - 1
mid = len(a) // 2
while low <= high:
if value == a[mid]:
high = mid - 1
result = mid + 1
elif value > a[mid]:
low = mid + 1
else:
high = mid - 1
mid = (low + high) // 2
return result
def right_binarnui_poisk (a,value): # правый бинарный поиск
result = -1
low = 0
high = len(a) - 1
mid = len(a) // 2
while low <= high:
if value == a[mid]:
low = mid + 1
result = mid + 1
elif value > a[mid]:
low = mid + 1
else:
high = mid - 1
mid = (low + high) // 2
return result
#ввод данных
N, K = map(int, input().split())
sp = [int(i) for i in input().split()]
sp1_ = [int(i) for i in input().split()]
# организация вывода
for i in range(K):
result_itog = [left_binarnui_poisk(sp,sp1_[i]),right_binarnui_poisk(sp,sp1_[i])]
if result_itog.count(-1): # если вывод равен [-1,-1], то выведет 0 по условию задачи
print(0)
else:
print(*result_itog)
Мне не нравится ответ автора вопроса. Не потому что он не правильный. Я его не тестировал, очевидных ошибок я нём не вижу. Мне он не нравится потому что использует странный дизайн: переменная result
, две проверки условия на цикл, отсутствие утверждений об инвариантах.
Мой вариант всегда возвращает позицию i
для вставки элемента v
в упорядоченный список a
, такую что a[:i] + [v] + a[i:]
сохраняет порядок. Если в списке a
есть одна или несколько таких позиций, bisect_left
возвращает минимальный такой индекс - самую левую позицию, bisect_right
возвращает максимальный индекс - самую правую позицию. Если обе функции вернули одинаковые значения, элемент v
в списке отсутствует.
Обе функции нафаршированы закомментированными утверждениями об инвариантах. Закомментированными потому что любое такое утверждение увеличивает время работы функции c log n до n log n.
В каждом цикле делается только одно сравнение. Это увеличивает производительность. Обычно второе условие проверяет равенство для раннего выхода из цикла. Можно показать что эта оптимизация экономит в среднем одну итерацию, увеличивая количество проверок в два раза. То есть, она замедляет поиск.
def bisect_left(a, v):
low = 0
high = len(a)
# assert a[:low] < v <= a[high:]
while low < high:
# assert a[:low] < v <= a[high:]
mid = (low + high) // 2
assert low <= mid < high
if a[mid] < v:
# assert a[:mid + 1] < v
low = mid + 1
# assert a[:low] < v
else:
# assert v <= a[mid:]
high = mid
# assert v <= a[high:]
# assert a[:low] < v <= a[high:]
# assert a[:low] < v <= a[high:]
assert low == high
return low
def bisect_right(a, v):
low = 0
high = len(a)
# assert a[:low] <= v < a[high:]
while low < high:
# assert a[:low] <= v < a[high:]
mid = (low + high) // 2
assert low <= mid < high
if v < a[mid]:
# assert v < a[mid:]
high = mid
# assert v < a[high:]
else:
# assert a[:mid + 1] <= v
low = mid + 1
# assert a[:low] <= v
# assert a[:low] <= v < a[high:]
# assert a[:low] <= v < a[high:]
assert low == high
return low
def bisect_range(a, v):
return bisect_left(a, v), bisect_right(a, v)
def main():
_, k = map(int, input().split())
a = tuple(map(int, input().split()))
for v in map(int, input().split()):
left, right = bisect_range(a, v)
if left == right:
print(0)
else:
print(left + 1, right)
main()
P.S. Это всё сделано для только для упражнения. Функции bisect.bisect_left
, bisect.bisect_right
есть в стандартной библиотеке.
P.P.S. bisect_left
и bisect_right
почти одинаковы. Их можно объединить в одну функцию, которая вместо значения v
принимает функцию-предикат p
. Код сократится. Не знаю можно ли сказать что он станет проще, но отлаживать его будет легче.
def bisect_pred(a, p):
low = 0
high = len(a)
# assert not p(a[:low]) and p(a[high:])
while low < high:
# assert not p(a[:low]) and p(a[high:])
mid = (low + high) // 2
assert low <= mid < high
if p(a[mid]):
# assert p(a[mid:])
high = mid
# assert p(a[high:])
else:
# assert not p(a[:mid + 1])
low = mid + 1
# assert not p(a[:low])
# assert not p(a[:low]) and p(a[high:])
# assert not p(a[:low]) and p(a[high:])
assert low == high
# assert not p(a[:low]) and p(a[low:])
return low
def bisect_range(a, v):
return (
bisect_pred(a, lambda a_i: a_i >= v), # bisect_left
bisect_pred(a, lambda a_i: a_i > v) # bisect_right
)