【问题标题】:Return the index of element using divide and conquer使用分治法返回元素的索引
【发布时间】:2021-12-27 21:14:43
【问题描述】:

所以我们有一个未排序的数组,其中每个元素是下一个元素的 ±1 (|A[i]-A[i+1]|

这是我想出的:

 def find(x,k,z):
    if(len(x) == 1 and x[0] != k):
        return -1
    mid = len(x)//2
    if x[mid] == k:
        return mid + z
    elif abs(x[mid] - k) <= 1:
        return find(x[:mid],k,mid+z)
    else:
        return find(x[mid:],k,mid+z)

这是我使用的数组 x = [1,2,3,4,5,4,3,3,2,3,4,5,6,7,8];该代码似乎适用于除 6 和 2 之外的每个元素,它返回 -1。

【问题讨论】:

  • 没有办法对此进行二分搜索。如果元素不在索引中间,则需要检查中间的左右两侧。然后检查左右的返回值,如果其中之一是注释-1,则返回它。如果两者都是 -1,则返回 -1。
  • 我们的目标是尝试在 O(log n ) 时间内做到这一点。
  • 我很确定二分搜索(至少以这种形式)并不能保证正确的结果。通过分而治之,您仍然可以利用 2 个相邻元素之间的差值最多为 1 的事实来实现对数时间复杂度。在每一步中,查看子数组的长度及其第一个索引处的值。如果该值与目标之间的差异大于子数组的长度,则可以确定目标不存在于该子数组中并为其返回 -1 而无需检查子数组的其余部分。
  • 这不可能比 O(n) 更好;考虑像@​​987654322@ 这样的列表,其中要搜索的目标元素是22 可以在列表中的任何偶数索引处,当您找不到它时,您只会看到一个 0 或 1,它不会告诉您 2 在哪里。因此,如果一个算法没有检查 O(n) 个位置,那么在 2 可能存在的位置仍然会有 O(n) 个位置。
  • @kaya3 因为元素的变化不能超过 1,所以永远不会出现许多重复增加数组大小而没有有效结果的情况。所以你对传统的二分搜索是正确的,但是我们可以做一个智能分区和递归......

标签: algorithm divide-and-conquer


【解决方案1】:

检查代码中的错误。 因此,请确保您了解 Python 列表切片的工作原理。 例如,

a = [0, 1, 2, 3, 4, 5]
print(a[:2])
# [0, 1]
print(a[2:])
# [2, 3, 4, 5]

这意味着当您执行x[:mid] 时,您排除列表末尾的中间元素。 但是x[mid:]你从一开始就包含中间元素。

这是你想做的吗?

另外,您可能会发现在递归调用中携带左、右索引比将左偏移量作为z 携带更容易理解(也更有效)。使用左、右有助于避免切片和偏移算法中的一系列边缘情况问题。

【讨论】:

  • 我尝试在我的调用中包括和排除中间,但它没有改变任何东西我仍然收到返回 -1 的元素 6 和 2 的错误
【解决方案2】:

寻找intermediate value theorem 的离散亲属的用例。

紧贴几乎已建立的最后一点信息的实现:

def _index_of(values, key, lo, dlo, hi, dhi):
    """ Return index of one occurrence of key in values from lo to hi. 
        Needs abs(values[i] - values[i+1]) <= 1, dlo/dhi == values[lo/hi] and
        (values[lo] < key < values[hi] or values[hi] < key < values[lo]). """
    while True:
        mid = lo + (hi - lo) // 2
        if mid <= lo:
            return -1
        value = values[mid]
        d = value - key
        # print(lo, dlo, mid, d, hi, dhi)
        if d * dlo <= 0:  # key should occur in values[lo:mid+1]
            if d == 0:
                return mid
            hi = mid
            dhi = d
        else:
            lo = mid + 1  # mid + abs(d) would be the only place sign mattered
            dlo = values[lo] - key
            if dlo == 0:
                return lo

def index_of(values, key, z=0):
    """ Return index of one occurrence of key in values, -1 if none. """
    # if values is None:
    #   return -1
    last = len(values) - 1
    if last < z:
        return -1
    dz = values[z] - key
    dl = values[last] - key
    if 0 <= dz * dl:
        if 0 == dz:
            return z
        if 0 == dl:
            return last
        raise "can't guarantee fast result"
    return _index_of(values, key, z, dz, last, dl)


if __name__ == '__main__':
    x = [1,2,3,4,5,4,3,3,2,3,4,5,6,7,8]
    def t(x):
        for v in set(x):
            print(v, index_of(x, v))
    t(x)

【讨论】:

    【解决方案3】:

    这是一种查找理论最大值和最小值以确定子列表是否甚至能够保存搜索值的方法。有了它,你可以修剪一些树枝。

    你的方法和这个的区别: 在这里,我们看两半。但也短路不可能的分支仍然尝试实现 O(log n)

    from math import ceil
    from typing import List
    
    def th_min_max(x: List[int], li: int, ri: int) -> int:
        """
        Find the theoretical maximum and minimum values
        for all elements between two known "anchor" values
        x is the data, where each abs(x[i] - x[i+1] <= 1)
        li, ri are the indexes of the anchors.
        """
        assert li <= ri
        # these are known values at the anchor locations.
        v1 = x[li]
        v2 = x[ri]
        # so how high/low can the numbers inbetween go?
        # the theoretical maximum is a sustained increase followed by a sustained decrease.
        num_changes = ri - li
        # some of the numbers are used to bridge difference between v1, v2
        remaining = num_changes - abs(v1 - v2)
        assert remaining >= 0, f"The input array does not meet constraints; look at [{li}] --> {v1} and [{ri}] --> {v2}"
        # so now we are look at maximum increase/decrease possible once we've bridged
        # the gap between the two known numbers.
        # half the remaining changes have to be used to 'undo' the initial changes
        # if there is an odd number, the remaining one can be max/min,
        # so use the ceil( ) function to round up.
        biggest_delta = int(ceil(remaining / 2))
        return (min(v1, v2) - biggest_delta, max(v1, v2) + biggest_delta)
    

    有了 min/max 函数,我们可以做一个分区算法:

    def find(x: List[int], li: int, ri: int, search: int) -> int:
        """
        finds _some_ index where the search value is found in x 
        between index li and index ri; -1 if not found.
        """
        # first, if li = ri, this is a trivial is this it? question
        if li == ri:
            return li if x[li] == search else -1
        # second, figure out if this range could possibly have the number
        mn, mx = th_min_max(x, li, ri)
        if search < mn:
            # value being looked for _cannot_ be in range [li, ri]
            return -1
        if search > mx:
            # value being looked for _cannot_ be in range [li, ri]
            return -1
        mid = int((li + ri)/2)
        # we ma have to look both ways, but let's start on the
        # side with the anchor value closer to search value
        if abs(x[li] - search) <= abs(search - x[ri]):
            # go left first, then right.
            first = find(x, li, mid, search)
            return first if first >= 0 else find(x, mid + 1, ri, search)
        else:
            # go right first, then left
            first = find(x, mid + 1, ri, search)
            return first if first >= 0 else find(x, li, mid, search)
    

    测试您的输入:

    d = [1,2,3,4,5,4,3,3,2,3,4,5,6,7,8]
    
    for search in d:
        res = find(d, 0, len(d)-1, search)
        print(search, res, d[res])
    

    -->

    1 0 1
    2 1 2
    3 6 3
    4 5 4
    5 11 5
    4 5 4
    3 6 3
    3 6 3
    2 1 2
    3 6 3
    4 5 4
    5 11 5
    6 12 6
    7 13 7
    8 14 8
    

    【讨论】:

      猜你喜欢
      • 2016-08-16
      • 2020-12-01
      • 2014-12-16
      • 1970-01-01
      • 2015-09-05
      • 2014-04-28
      • 2022-10-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多