【问题标题】:An array exercise数组练习
【发布时间】:2012-07-06 17:35:14
【问题描述】:

我试图解决这个问题: 给定一个 sorted 数组,其中包含从 0 开始的 连续 整数(一个整数可以重复多次)例如 - 0,0,0,1,2,3,3,3,4,4(也可以很长 - 这只是一个example) ,有效地找到给定整数的开始和结束索引。

我正在考虑使用

1)遍历(复杂度 = O(n))

2) 修改后的二分查找(复杂度 =O(log n))。 [ n = 总数组长度]

然后想知道是否可以利用连续整数属性来解决它。 有什么不同的想法或建议吗?

【问题讨论】:

  • 只有当序列长度远大于最大数时,二分查找才有用。
  • 同意如果序列长度非常小,二分查找就没有多大用处,还有其他想法吗?
  • 我会跳成 3 组,因为您可以先看 1 组,然后再看 1 组。这样,如果您是 OOB,您可以停下来,并且会降低您以更快的速度跳... (1/3 的迭代次数。)当然,向前和向后看会花费一些成本,但不如像您那样多次迭代循环。
  • @Fallenreaper:OOB?当然这会减少方法 1 的迭代次数,但它会比二分搜索方法慢...
  • 真的,最快的方法是在对数组进行排序时标记这些索引!

标签: arrays algorithm language-agnostic


【解决方案1】:

首先,让我们忽略“连续性”属性

只要问题在于找到处理单个单个请求的最有效方法,直接的通用解决方案将是执行两个连续的二进制搜索:第一个找到序列,第二个找到序列的结尾。第二次搜索在数组的其余部分执行,即在先前找到的序列开头的右侧。

但是,如果您以某种方式知道序列的平均长度相对较小,那么将第二次二分搜索替换为 线性 搜索就开始有意义了. (这与合并两个相似长度的排序序列时的原理相同:线性搜索优于二分搜索,因为输入的结构保证平均搜索目标位于序列开头附近)。

更正式地说,如果整个数组的长度是n,并且数组中不同整数值的数量(多样性指标)是k,那么平均而言,当n/k 时,线性搜索开始优于二分搜索变得比log2(n) 小(可能需要一些依赖于实现的常数因子来建立实际关系)。

说明这种效果的极端例子是n=k 时的情况,即当数组中的所有值都不同时。显然,使用线性搜索找到每个序列的结尾(一旦你知道开始)将比使用二分搜索更有效。

但这需要额外了解输入数组的属性:我们需要知道k

这就是你的“连续性”属性发挥作用的时候!

由于数字是连续的,因此数组中的最后一个值减去数组中的第一个值等于k-1,即

k = array[n-1] - array[0] + 1

此规则也可以应用于原始数组的任何子数组,以计算该子数组的多样性指标。

这已经为您提供了一种非常可行且高效的序列查找算法:首先对序列的开头执行二进制搜索,然后执行 binarylinear 根据nk 之间的关系进行搜索(或者更好的是,在右子数组的长度和右子数组的品种度量之间)。

附:同样的技术也可以应用于第一次搜索。如果您正在寻找i 的序列,那么您立即知道它是数组中的第j 序列,其中j = i - array[0]。这意味着对该序列开头的线性搜索平均需要j * n/k 步。如果这个值小于log2(n),线性搜索可能比二分搜索更好。

【讨论】:

    【解决方案2】:

    您可以查看开始和结束元素以查看数组中有多少个元素k,然后对每个边界进行修改后的二进制搜索,检查元素及其左右.我认为你不能比 O(k log(n)) 更快。

    如果您按从左到右(或从右到左)的顺序搜索边界,则应该减少平均时间,因为您可以在数组的较小子集中进行搜索,但我认为这不会影响最坏情况的复杂性。

    即检查索引i及其相邻元素,寻找0-1边界:

    |0|0|0|1|1|1|1|1|2|2|2 \ 一世 /

    然后向左走:

    |0|0|0|1|1|1|1|1|2|2|2 \ 一世 /

    既然你找到了,现在在它右边的集合中寻找 1 和 2 之间的边界:

    1|1|1|1|1|2|2|2 \ 一世 / 1|1|1|1|1|2|2|2 \ 一世 /

    你已经完成了,因为你知道你在寻找多少边界。

    编辑:对不起,我没有意识到你只想要一个边界。该过程是相似的-您找到要搜索的元素(即 O(log(n)) ),然后使用相同的修改后的搜索向左和向右搜索以检查边界。根据数组的整体大小与数组中的项目数,实际上只检查一侧或另一侧可能会更快。

    【讨论】:

    • 我不明白,我只能找到一个特定整数的边界,那为什么要找到所有呢?
    • 0-1边界的查找应该与查找整数的复杂度相同。我认为找到边界的复杂性是 O(log(n)) + O(log(n)); 1 表示下限,1 表示上限。
    • O(log(n))+O(log(n)) 与 O(log(n)) 相同。
    • @airza 是的。但是你写了 O(k*log(n));那么k是什么?
    • @user1416970 找到边界很容易;而不是搜索x==seq[mid] 尝试x==seq[mid] && x-1==seq[mid-1]
    【解决方案3】:

    您的数组已排序,因此您可以使用二分搜索。假设n 是数组A 的大小,将A 分为A1A2,大小分别为(n/2) 或分别为(n/2) 和(n/2) + 1,如果n 是奇数.您正在寻找 j 的起始索引。 (j 在 A 中)

     1. if A1(n/2) < j, then you know that j is in A2. 
     2. if A2(1) > j, then you know that j is in A1. 
     3. If A2(1) = j, then j may be in A1 and A2. Just check A1(n/2).
         if A1(n/2) < j then 2. Do the same recursively on A2 to find the last index
         else apply dichotomic search to find the starting index and ending index in both arrays
    

    【讨论】:

    • 这正是我考虑过的修改后的二分搜索!同样我们也可以找到数字的结束索引。
    【解决方案4】:

    它必须是一种算法吗?你的问题只是说找到位置,但如果它确实是某种形式的算法方法,请告诉我,因为我提供了一个 PHP 函数,它以相当标准的方式处理这个问题。

    $numbers = array(0,0,0,1,2,3,3,4,4,4,4);
    function get_boundaries($array,$number)
    {
        $keys = array_keys($array,$number);
        $found = count($keys);
        if($found == 0)
        {
            $ret = false;
        }
        else if($found == 1)
        {
            $ret = array($keys[0],$keys[0]);
        }
        else if($found > 1)
        {
            $ret = array($keys[0],$keys[$found-1]);
        }
        return $ret;
    }
    
    $result = get_boundaries($numbers,1);
    print_r($result);
    
    
    //Result when looking for 0
    Array
    (
        [0] => 0
        [1] => 2
    )
    
    //Result when looking for 1
    Array
    (
        [0] => 3
        [1] => 3
    )
    
    //Result when looking for 9
    (boolean) false
    

    【讨论】:

    • 感谢您的代码,但我只是在思考想法,而不是在这里寻找解决方案的代码。我的意思是寻找解决某人可能遇到的问题的任何想法。
    猜你喜欢
    • 1970-01-01
    • 2018-09-15
    • 2021-06-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-10
    • 2023-03-21
    • 1970-01-01
    相关资源
    最近更新 更多