【问题标题】:Given an array V, we need to find two indices (i,j) such that V[j] > V[i] and (j - i) is maximum给定一个数组 V,我们需要找到两个索引 (i,j) 使得 V[j] > V[i] 和 (j - i) 最大
【发布时间】:2012-10-02 12:32:46
【问题描述】:

给定一个数组 V,我们需要找到两个索引 (i,j),使得 V[j] > V[i] 和 (j - i) 最大。

蛮力方法非常简单,其中对于索引 i 处的每个值(范围从 1 到 n),我们比较索引 j 处的值(范围从 i+1 到 n)。到目前为止,我们跟踪最大值 (j-i) 以找到最终答案。

这种方法的时间复杂度为 O(n^2)。有人对提高时间复杂度有什么建议吗?

【问题讨论】:

  • @spacevillain 如果列表为 [4, 3, 2 ,1 ],您的方法将不起作用,因为按非递减顺序对其进行排序后,i 将是 3,j 将0,这是不正确的,因为j 应该大于i
  • @Kay,V[i] 的限制是什么?
  • @IvanBenko - 如果您指的是数组的值,则没有限制。我们只需要找到一对索引(i,j),使得 A[j] > A[i],并且 j-i 最大(最大化)。
  • [8, 7, 5, 4, 2, 1, 9, 6, 3] 是一个非常难以搜索的列表。
  • 我可以在 O(n) 中找到一个很好的 j - i 下限。首先,按升序查找两个连续的条目。然后,从数组的末尾返回,寻找一个大于较低条目的条目。然后,从较低的条目返回,寻找低于较高条目的条目。

标签: algorithm


【解决方案1】:

这是一种可以在线性时间内解决问题的方法。

  1. 计算位置递增的堆栈 S i 使得 min A[1..i-1] > i 对数组进行简单的前向扫描。
  2. 向后迭代列表。
  3. 当当前元素大于栈顶 S 给定的值时:检查是否有新记录并弹出栈顶。

python中的快速实现:

def notsurewhattonamethis(A):
    assert(A)
    S = [0]
    for i,v in enumerate(A):
        if v<A[S[-1]]:
            S.append(i)
    best = (-1,-1)
    for i,v in reversed(list(enumerate(A))):
        while v>A[S[-1]]:
            j = S.pop()
            d = i - j
            if d > best[1]-best[0]:
                best = (j,i)
            if not S: return best
    return best

【讨论】:

  • 很好,虽然它仍然是 O(N^2) 时间*空间复杂度。
  • @Neil:你为什么还要关心时间*空间复杂度?
  • 只是因为最初的问题没有说明 O(N) 空间复杂度是否可以接受。 (鉴于已接受的答案,我明白了。)
【解决方案2】:

如果您知道数组元素的限制(请参阅下面的更新)我可以建议您使用时间复杂度 O(n*log(MaxN)) 和空间复杂度 O(MaxN) 的算法,其中 MaxN = Max(V[i ])。 对于这个算法,我们需要能够在时间复杂度为 O(log(N)) 的数组中获得最小值的结构,并以时间复杂度 O(log(N)) 更新数组元素。 Fenwick tree 可以做到这些。我们称这个结构为 minimizator。然后我们需要:

  1. 按给定顺序 v[i] 迭代所有元素,并将其放在 v[i] 位置的最小值 i 处。
  2. 对于每个元素 v[i],使用 1 和 v[i-1] 之间的最小化器找到最小值(这是小于 v[i] 的元素的最小索引)
  3. 记住 i 与找到的小于 v[i] 的元素的最小索引之间的最大差异。

好的。我试过写一些伪代码

prepare array (map values)
init minimizator

ansI = -1
ansJ = -1

for i from 0 to v.length-1
  minIndexOfElementLessThanCurrent = get min value from 1 to v[i]-1 (inclusive) using minimizator
  set to minimizator v[i] position value i

  if minIndexOfElementLessThanCurrent is exists
    if ansJ - ansI < i - minIndexOfElementLessThanCurrent 
      ansJ = i
      ansI = minIndexOfElementLessThanCurrent

C++ 实现:

class FenwickTree
{
    vector<int> t;
    int n;

public:

    static const int INF = 1000*1000*1000;

    void Init (int n)
    {
        this->n = n;
        t.assign (n, INF);
    }

    int GetMin (int i)
    {
        int res = INF;
        for (; i >= 0; i = (i & (i+1)) - 1)
            res = min (res, t[i]);
        return res;
    }

    void Update (int i, int value)
    {
        for (; i < n; i = (i | (i+1)))
            t[i] = min (t[i], value);
    }
};

pair<int, int> Solve(const vector<int>& v)
{
    int maxElement = 0;
    for(int i = 0; i < v.size(); i++)
        maxElement = max(maxElement, v[i]);

    FenwickTree minimizator;
    minimizator.Init(maxElement+1);

    int ansI = -1, ansJ = -1;
    for(int i = 0; i < v.size(); i++)
    {
        int minLeftIndex = minimizator.GetMin(v[i]-1);      
        minimizator.Update(v[i], i);

        if(minLeftIndex == FenwickTree::INF) continue; // no left elements less than v[i]

        if(ansJ - ansI < i - minLeftIndex)
        {           
            ansJ = i;
            ansI = minLeftIndex;
        }
    }
    return make_pair(ansI, ansJ);
}

更新: 如果元素的种类不是 int(f.e. double) 或者数组元素的最大值太大 (f.e. 10^9) 我们可以 将数组值(不会影响结果)映射到整数集1..N,然后时间复杂度应该是O(n * log(n))

更新:

如果元素是整数 - 有O(max(maxN, n)) 解决方案。所以如果maxN &lt;= n 复杂度是O(N)。我们只需要在 const time O(1) 内回答“get minimum from 1 to N”这个查询:

  1. 创建大小为maxN的数组
  2. 数组的元素 m[i] 是源数组 V 中 i 值的最小索引。
  3. 使用动态编程创建与数组元素r[i] 最小为m[j], 1 &lt;= j &lt;= i 大小相同的数组。递归关系为r[i] = min(r[i-1], m[i])

这个算法的主要思想和上面一样,只是用数组r求从1v[i]的最小值。

C++ implementation:

pair<int, int> Solve(const vector<int>& v)
{
    int maxElement = 0;
    for(int i = 0; i < v.size(); i++)
        maxElement = max(maxElement, v[i]);

    vector<int> minimum(maxElement + 1, v.size() + 1);
    for(int i = 0; i < v.size(); i++)
        minimum[v[i]] = min(minimum[v[i]], i); // minimum[i] contains minimum index of element i

    for(int i = 1; i < minimum.size(); i++)
        minimum[i] = min(minimum[i-1], minimum[i]); // now minimum[i] contains minimum index between elements 1 and i

    int ansI = -1, ansJ = -1;
    for(int i = 0; i < v.size(); i++)
    {
        int minLeftIndex = minimum[v[i]-1];      

        if(minLeftIndex >= i) continue; // no left elements less than v[i]

        if(ansJ - ansI < i - minLeftIndex)
        {           
            ansJ = i;
            ansI = minLeftIndex;
        }
    }
    return make_pair(ansI, ansJ);
}

如果元素是双精度的,或者其他的东西(非常大的整数),我们不能在线性时间内将元素映射到设置 1..N(或者可以吗?)。我只知道O(n*log(n)) 解决方案(排序元素等)

【讨论】:

  • +1 表示算法,几乎 -1 表示执行不佳。最好用伪代码写
【解决方案3】:

Java 实现以线性时间运行。

public class MaxIndexDifference {

public static void main(String[] args) {
     System.out.println(betweenTwoElements(2, 3, 6, 10, 4));
}

private static int betweenTwoElements(int... nums) {
    int numberOfElements = nums.length;
    int maxDifference = -1, minIndex = 0, maxIndex = 0;

    int[] lMin = new int[numberOfElements];
    int[] rMax = new int[numberOfElements];

    /* Construct lMin such that stores the minimum value (to the left)  from (nums[0], nums[1], ... nums[i])*/

    lMin[0] = nums[0];
    for (int i = 1; i < numberOfElements; i++) {
        lMin[i] = Math.min(nums[i], lMin[i -1]);
    }
    /* Construct RMax[] such that RMax[j] stores the maximum value (to the right) from (arr[j], arr[j+1], ..arr[n-1]) */
    rMax[numberOfElements - 1] = nums[numberOfElements - 1];
    for (int i = numberOfElements-2; i >= 0; i--) {
        rMax[i] =  Math.max(nums[i], rMax[i + 1]);
    }
    /* Traverse both arrays from left to right to find optimum maxIndex - minIndex This process is similar to merge() of MergeSort */
    while (minIndex < numberOfElements && maxIndex < numberOfElements) {
        if (lMin[minIndex] < rMax[maxIndex]) {
            maxDifference = Math.max(maxDifference, maxIndex - minIndex);
            maxIndex = maxIndex +1;
        } else {
            minIndex = minIndex +1;
        }           
    }   
    return maxDifference;
}
}

【讨论】:

    【解决方案4】:

    复杂度为 O(N) 的算法:

    #!/usr/bin/perl
    
    use strict;
    use warnings;
    
    sub dump_list { print join(", ", map sprintf("%2d", $_), @_), "\n" }
    
    for (0..20) {
        # generate a random list of integers with some convenient bias:
        my @l = (map int(rand(20) + 20 - $_), 0..19);
    
        my $max = $l[-1];
        my $min = $l[0];
    
        my @max;
        for my $l (reverse @l) {
            $max = $l if $l > $max;
            push @max, $max;
        }
        @max = reverse @max;
    
        my @min;
        for my $l (@l) {
            $min = $l if $l < $min;
            push @min, $min;
        }
    
        my $best_i = 0;
        my $best_j = -1;
        my $best   = -1;
    
        my $j = 0;
        for my $i (0..$#l) {
            while ($j < @l) {
                last unless $max[$j] > $min[$i];
                $j++;
                if ($j - $i > $best) {
                    $best = $j - 1 - $i;
                    $best_i = $i;
                    $best_j = $j - 1;
                }
            }
        }
    
        print "list: "; dump_list @l;
        print "idxs: "; dump_list 0..$#l;
        print "best: $best, i: $best_i, j: $best_j\n\n";
    }
    

    更新:响应 Nohsib 请求:

    假设你有一个随机的数字列表(a[0]、a[1]、a[2]、a[3]...、a[N-1])

    第一步是为每个数字找到左边的最大值为 mas max[i] = maximum(a[0], a[1], ..., a[i]) 和右边的最小值 min[i] = minimum(a[i], a[i+1], ..., a[N-1])

    一旦你有了这些数组,找到最大化k-ja[j] &lt; a[k] 的区间几乎是微不足道的。

    试着用一些随机列表在纸上做,你会很容易发现背后的逻辑。

    【讨论】:

    • 太棒了!我想如果不使用 for 和 while 循环,你有一个像 while ( i &lt; n &amp; j &lt; n) 这样的 while 循环并在 $max[$j] &gt; $min[$i] 时增加 j 否则增加 i,这仍然可以工作。目前使用您的方法,对于[1,2,3,4,4,4,4,4,4,4] 之类的列表,您可能仍会获得 O(n^2) 的时间复杂度。但是通过上述建议的更改,您可以将时间复杂度降低到 O(n)。谢谢你的主意。有空我会试着写下伪代码。
    • @Kay:在最坏的情况下它已经是 O(N),因为 $j 在 for 循环内没有重置为 0。在最坏的情况下,while 循环中的代码会执行 2*N 次,因此它是 O(N)。
    【解决方案5】:

    我不确定您在这里真正想要什么,您问题的第一段与第二段冲突。所以我会给出两个答案,一个针对我能想到的每一种解释,尽管两者可能都不是你的意思。

    第一种解释:在 j > i 的约束下,寻找一个极大的 V[j]-V[i]。
    这是 几乎 找到最小值和最大值。但除此之外,对索引也有限制。这本身并不意味着这个想法不能被使用。对于任何选择的 V[i],您只需要 V[i+1 .. n] 上的最大值,并且您不需要每次都重新计算这些最大值,从而得到这个算法:

    • 在 O(n) 中计算 suffix-max 数组
    • 找到 O(n) 中差异最大的对 (V[i],suffixmax[i+1])

    第二种解释:在V[j] > V[i]的约束下寻找一个极大的j-i。
    我想不出什么好东西。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-04-05
      • 1970-01-01
      • 2013-08-19
      • 2012-10-20
      • 2021-04-09
      • 2021-11-01
      • 2019-04-19
      相关资源
      最近更新 更多