【问题标题】:Return index of all matching values sorted list返回所有匹配值排序列表的索引
【发布时间】:2021-12-05 23:22:23
【问题描述】:

我最近在一次采访中遇到了这个问题,但一直无法解决。给定一个排序列表:

li = [1,2,2,3,4,4,4,4,5,5,5...]

返回与目标匹配的所有元素的索引ex. 4,时间复杂度为 O(log n)。

这个问题的设置告诉我这是一个二分搜索问题。我用类似下面的东西回答了这个问题,但还没有找到更好的答案:

data = [2,3,5,6,8,9,12,12,12,14,17,19,22,25,27,28,33,37]
target = 12

def binary_search(data, target):
    low = 0 
    high = len(data) - 1

    while low <= high:
        mid = (high + low) // 2
        if data[mid] == target:
            return mid
        elif data[mid] > target:
            high = mid - 1
        else:
            low = mid + 1
    
    return False

def return_other_indices(data, target, match_index):
    output = [match_index]

    i = match_index + 1
    while data[i] == target:
        output.append(i)
        i += 1

    i = match_index - 1
    while data[i] == target:
        output.append(i)
        i -= 1

    return output

match_index = binary_search(data, target)
output = return_other_indices(data, target, match_index)
print(output)

有更好的方法吗?

【问题讨论】:

  • 您可以使用bisect 模块吗?
  • 请定义“更好”
  • @Jab 理想情况下完全不使用第二个函数,或者至少不使用两个 while 循环。

标签: python binary-search


【解决方案1】:

我不确定这是否是您要寻找的,但这里有一个使用 python 标准库的解决方案。

仅使用bisect

from bisect import bisect_left, bisect
start = bisect_left(data, target)
stop = bisect(data, target, lo=start)
output = range(start, stop)
print(list(output))

输出:[6,7,8]

旧答案:

使用bisect.bisect_left查找起点,itertools.takewhile获取所有元素。

from bisect import bisect_left
from itertools import takewhile, islice
left = bisect_left(data, target)
[left]+[i[0]+left+1 for i in takewhile(lambda x: x[1]==target,
                                       enumerate(islice(data, left+1, len(data))))]

注意。 takewhile 的线性版本在预期只有几个目标并且数据很大时可能会更快,否则双等分通常会更快

【讨论】:

  • 这似乎不是O(log n) 使用takewhile()
  • 嗯,我想这取决于目标的数量。我假设 O(log n) 部分是找到第一个目标。如果你期望有很多目标(可能高达len(data),那么你必须执行多个平分来找到边界。让我添加一个更新
  • @sj95126 感谢您的反馈,我更新了仅使用 bisect 的答案
  • @mozway 虽然这个答案非常适合正常使用,但面试官不太可能允许这个答案
  • @AbhinavSinghal 如果我是面试官,我可能会允许,然后询问bisect 将如何实施。了解库很重要
【解决方案2】:

首先,您必须考虑什么是二分搜索,以及它是如何工作的。现在应用于您必须修改查找第一个和最后一个的问题。

在此基础上,对第一个匹配项执行搜索,然后应用二分搜索对最后一个匹配项执行搜索。

通过拥有两个索引,我可以简单地打印区间之间的数字

def findFirstOccurrence(nums, target):
    (left, right) = (0, len(nums) - 1)
    result = -1
    while left <= right:
        mid = (left + right) // 2
        if target == nums[mid]:
            result = mid
            right = mid - 1
        elif target < nums[mid]:
            right = mid - 1
        else:
            left = mid + 1
    return result
    
def findLastOccurrence(nums, target):
    (left, right) = (0, len(nums) - 1)
    result = -1
    while left <= right:
        mid = (left + right) // 2
        if target == nums[mid]:
            result = mid
            left = mid + 1
        elif target < nums[mid]:
            right = mid - 1
        else:
            left = mid + 1
    return result


if __name__ == '__main__':
 
    nums = [2, 5, 5, 5, 5, 5, 6, 6, 8, 9, 9, 9]
    target = 5
    
    indexFirst = findFirstOccurrence(nums, target)
    indexLast = findLastOccurrence(nums, target)
    
    if(indexLast == -1 or indexFirst == -1):
        print("Element not found")
    else:
        print(*range(indexFirst,indexLast+1))

更新

这是上面的优化形式:

def optimicedOccurrence(nums, target, type_of_occurrence):
    (left, right) = (0, len(nums) - 1)
    result = -1
    while left <= right:
        
        mid = (left + right) // 2
        if target == nums[mid]:
            result = mid
            if(type_of_occurrence):
                right = mid - 1
            else:
                left = mid + 1
        elif target < nums[mid]:
            right = mid - 1
        else:
            left = mid + 1
    return result


if __name__ == '__main__':
 
    nums = [2, 5, 5, 5, 5, 6, 6, 8, 9, 9, 9]
    target = 5
    
    indexFirst = optimicedOccurrence(nums, target, 1)
    indexLast = optimicedOccurrence(nums, target, 0)
    
    if(indexLast == -1 or indexFirst == -1):
        print("Element not found")
    else:
        print(*range(indexFirst,indexLast+1))

【讨论】:

  • 很好的答案! ...但是打印将是 O(n) 。打印所有索引没有意义,因为它无论如何都是一个排序列表。如果元素存在,我们可以打印最左边和最右边的索引。
  • @AbhinavSinghal,是的,你是对的,但问题是:返回与目标匹配的所有元素的索引。这是我认为最好的解决方案。
【解决方案3】:

使用 bisect 模块:

from bisect import bisect_left,bisect_right

li = [1,2,2,3,4,4,4,4,5,5,5]

target = 4
print(*range(bisect_left(li,target),bisect_right(li,target)))

4 5 6 7

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-10-31
    • 2019-01-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-02
    相关资源
    最近更新 更多