【问题标题】:Lowest value in range范围内的最小值
【发布时间】:2013-11-14 09:48:22
【问题描述】:

我想找到某个范围内的最低值。
每次都要迭代数组还是有什么动态方法?

假设我有输入数组:

index: 0 1 2 3 4 5 6 7
value: 1 4 6 1 6 7 2 3

然后我必须在 (包括)范围内选择最小的。例如:

min(0,7) = 1
min(0,2) = 1
min(4,6) = 2
min(1,2) = 4

我对最快的解决方案感兴趣,最好在恒定时间内得到结果。

在此期间不会更改数组。

【问题讨论】:

  • 看来你只需要for循环
  • 那是线性的......难道不能走得更快吗?
  • 您可以在O(N^2) 时间内为每个可能的范围预先计算一次结果。在那之后,查找将是恒定的时间。根据您需要多久查看一次相同的数据,您或许可以摊销初始成本。
  • 值未排序。您确定可以使用 binsearch 吗?
  • @IgorTandetnik 不适用于具有 1 000 000 个元素的数组:/

标签: c++ arrays algorithm minimum


【解决方案1】:

如果您要对同一组数字执行多个查询,那么您将需要构造一个Cartesian Tree

笛卡尔树可用作范围最小查询的有效数据结构的一部分,范围搜索问题涉及在原始序列的连续子序列中要求最小值的查询。

正如文章所说,查询可以在恒定时间内执行。

【讨论】:

  • 结构不错,但我不确定如何在 constant 时间内执行查询。我不会对 O(D) 感到惊讶,其中 D 是树的深度……但常数似乎使它变薄了。在原始文章中,如果我要求从 9 到 18(即 1)范围内的最小值,似乎需要更多时间才能确定 10 是 [12,10,20,15] 中的最小值。
  • @MatthieuM.:这很棘手。查找涉及查找树中查询的边界节点的最低共同祖先。如您所料,它的简单实现是 O(D),但是有一些技巧可以在 O(1) 中找到 LCA,请参阅en.wikipedia.org/wiki/Lowest_common_ancestor
【解决方案2】:

您可以使用segment tree 回答这个问题。 This 是关于分段树和范围最小查询的最佳教程之一。

我给出的是JAVA实现,代码一目了然,如有疑问请告诉我。

public class SegmentTree {

    private int[] array;
    private int length;

    public static SegmentTree initialize(int[] a) {
        return new SegmentTree(a);
    }

    private SegmentTree(int[] a) {
        length = a.length - 1;
        int l = (int) (Math.log(a.length) / Math.log(2));
        l = (int) (Math.pow(2, l + 1) * 2 - 1);
        array = new int[l];
        initialize(a, 0, a.length - 1, 0);
    }

    private int initialize(int[] a, int p, int r, int index) {
        if (p == r) {
            array[index] = a[p];
            return a[p];
        }
        int q = p + (r - p) / 2;
        array[index] = Math.min(initialize(a, p, q, 2 * index + 1), initialize(a, q + 1, r, 2 * index + 2));
        return array[index];
    }

    public int findMin(int p, int r) {
        return _findMin(p, r, 0, length, 0);
    }

    private int _findMin(int qs, int qe, int ss, int se, int i) {
        if (qs <= ss && se <= qe) {
            return array[i];
        }
        if (qs > se || qe < ss) {
            return Integer.MAX_VALUE;
        }
        int q = ss + (se - ss) / 2;
        return Math.min(_findMin(qs, qe, ss, q, 2 * i + 1), _findMin(qs, qe, q + 1, se, 2 * i + 2));
    }

    private void print() {
        int index = 0;
        for (int k : array) {
            System.out.println(index + ":" + k);
            index++;
        }
    }

    public static void main(String[] args) {
        int[] a = {1, 34, 5, 6, 78, 5, 67, 89};
        SegmentTree s = initialize(a);
        System.out.println(s.findMin(2, 4));
    }
}

【讨论】:

  • 阅读文章后代码变得容易理解了。尽管如此,在解释时命名变量qs, qe, ss, q 并不是一个好主意(我也喜欢简单函数的短名称)。 cmets也将有很大帮助。无论如何感谢这个对数解决方案,+1 :)
【解决方案3】:

您可以尝试以下方法(不确定我是否遗漏了什么)

如果允许您进行一些预处理,那么您可以拥有一组局部最小值(谷)。

该数组应根据各自的索引进行排序。

一旦得到一个区间,就对最小值数组中的较低索引执行二进制搜索。选择该数组中较大的一个。说I1

然后对最小值数组中较高的给定索引执行二进制搜索。选择该数组中较小的那个。说I2

如果 I2 >= I1,则区间中至少存在一个局部最小值。只需比较该区间内的最小值和边界处的值即可得到答案。

否则,区间中不存在局部最小值。在这种情况下,最小值应位于区间的边界。只需比较两个值并得到较低的值。

【讨论】:

    【解决方案4】:

    这是一项标准任务。您可以使用标准库功能。它应该是最快的。来自here的例子:

    #include <iostream>     // std::cout
    #include <algorithm>    // std::min_element, std::max_element
    
    int main () {
      int myints[] = {3,7,2,5,6,4,9};
    
      std::cout << "The smallest element is " << *std::min_element(myints,myints+7) << '\n';
    
      return 0;
    }
    

    附:您无法在恒定时间内获得结果,因为如果您需要 N 个元素之间的最小值,则需要检查每个元素。最小任务复杂度为 O(N)。

    【讨论】:

    • 线性解。我在问一些更快的事情(如果可能的话)
    • 好吧。如果有更快的东西,那就是标准。
    • 视情况而定。也许有一个动态的解决方案。这些在 std 中并不常见,afaik。
    • 无论如何它不能比线性快,你需要检查每个元素至少一个。
    • 当然不是。您可以预先生成一些辅助数组,然后使用它们在更短的时间内回答。阅读前缀和。 en.wikipedia.org/wiki/Prefix_sum
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-03-16
    • 2013-03-26
    • 2020-07-18
    • 2020-10-03
    • 1970-01-01
    • 2015-10-25
    • 2014-03-22
    相关资源
    最近更新 更多