【问题标题】:Highest value of last k values in array, better than O(n^2)数组中最后 k 个值的最大值,优于 O(n^2)
【发布时间】:2014-01-20 01:31:00
【问题描述】:

我有一个包含 n 个元素的数组,对于每个元素,我试图从最后 k 个值(包括当前值)中找到最大值?例如。给定 k,

int[] highestValues = new int[arr.Length];
for (int i = k - 1; i < arr.Length; i++)
{
    int highest = Int32.MinValue;
    for (int j = i - k; j <= i; j++)
    {
        highest = Math.Max(highest, arr[j]);
    }
    highestValues[i] = highest;
}

看来这个算法的复杂度是 O(n^2)。我想知道是否有任何方法可以更快地做到这一点,例如与其在内部循环中比较 k 个数字,不如重用前一个操作的结果。

【问题讨论】:

标签: arrays algorithm


【解决方案1】:

对于这个问题有一个很好的解决方案,使用 Dequeue 数据结构(支持在第一个和最后一个位置添加和检查元素的结构)。

我们创建类 Entry 来存储元素的索引和该元素的值。观察到时间复杂度大约是 2*n = O(2*n) = O(n) (每个元素只添加一次到队列中,我们也遍历队列中的每个元素一次:在队列的第一个或最后一个位置将其删除)。

伪代码:

class Entry {
    int value, index;
}

int [] highestValues;

Dequeue queue = new Dequeue();   

 for(int i = 0; i < arr.Length; i++){
        Entry en = new Entry(arr[i], i);
        while(queue.last().value <= en.value){//Remove all elements smaller than the current element.
            queue.removeLast();
        }
        queue.append(en);
        if(i >= k){
            highestValues[i] = queue.first().value;
            while(queue.first().index < i - k){//Remove all elements which has index out of the range
                 queue.removeFirst();
            }
        }
    }    
}

【讨论】:

  • 1) 这不是伪代码,它是 java 代码。 2)这是滑动窗口内的最大值问题。
  • @maythesource.com 都是正确的:) 代码是不可编译的,所以至少伪代码的东西会让读者不会盲目地复制代码。
【解决方案2】:

这是最大滑动窗口问题滑动窗口最大值数组

见:

其中的关键是双端队列,它允许从头到尾移除元素,以便在我们滑动时保持最大值。

来源示例:

import java.util.ArrayDeque;
import java.util.Deque;

公共类滑动窗口 {

public static void maxSlidingWindow(int A[], int n, int w, int B[]) {
    Deque<Integer> Q = new ArrayDeque<Integer>();

    // Initialize deque Q for first window
    for (int i = 0; i < w; i++) {
        while (!Q.isEmpty() && A[i] >= A[Q.getLast()])
            Q.pollLast();
        Q.offerLast(i);
    }

    for (int i = w; i < n; i++) {
        B[i - w] = A[Q.getFirst()];

        // update Q for new window
        while (!Q.isEmpty() && A[i] >= A[Q.getLast()])
            Q.pollLast();

        // Pop older element outside window from Q
        while (!Q.isEmpty() && Q.getFirst() <= i - w)
            Q.pollFirst();

        // Insert current element in Q
        Q.offerLast(i);
    }
    B[n - w] = A[Q.getFirst()];
}

public static void main(String args[]) {
    int k = 20;

    int a[] = new int[100];
    for(int i=0; i<100; i++)
    a[i] = i;

    int b[] = new int[a.length - k + 1];

    maxSlidingWindow(a, a.length, k, b);

    System.out.println("Sliding Window Maximum is ");
    for (int i = 0; i < b.length; i++) {
        System.out.print(b[i] + ",");
    }

}

}

【讨论】:

  • 不错的链接,加上将索引添加到队列而不是值 +1 的想法
【解决方案3】:

我有一个我认为可行的实现(用 C# 编码):

public static int[] LastKHighestValues(int[] array, int k)
{
    int[] maxes = new int[array.Length];

    int indexOfCurrentMax = 0;
    int indexOfNextMax = 0;

    for (int i = 0; i < array.Length; i++)
    {
        if (indexOfNextMax <= indexOfCurrentMax ||
            array[i] > array[indexOfNextMax])
        {
            indexOfNextMax = i;
        }
        if (i - indexOfCurrentMax >= k)
        {
            indexOfCurrentMax = indexOfNextMax;
        }

        if (array[i] > array[indexOfCurrentMax])
        {
            indexOfCurrentMax = i;
        }


        maxes[i] = array[indexOfCurrentMax];
    }

    return maxes;
}

这个想法是你保留两个“指针”:一个指向当前最大值,一个指向当前最大值到期后要转到的下一件事。这可以通过数组一次性实现(所以 O(n))。

我有一些通过测试,但我不能确定我已经涵盖了所有极端情况:

Puzzle.LastKHighestValues(new[] {4, 3, 1}, 1).Should().Equal(new[] {4, 3, 1});
Puzzle.LastKHighestValues(new[] { 7, 7, 7 }, 3).Should().Equal(new[] { 7, 7, 7 });
Puzzle.LastKHighestValues(new[] { 7, 7, 7 }, 4).Should().Equal(new[] { 7, 7, 7 });
Puzzle.LastKHighestValues(new[] { 3, 2, 1 }, 2).Should().Equal(new[] { 3, 3, 2 });
Puzzle.LastKHighestValues(new[] { 7, 1, 4, 1, 1 }, 3).Should().Equal(new[] { 7, 7, 7, 4, 4 });
Puzzle.LastKHighestValues(new[] { 7, 8, 4, 9, 10 }, 2).Should().Equal(new[] { 7, 8, 8, 9, 10 });

【讨论】:

  • 这个解决方案似乎对我有用。也是最优雅的。希望它没有缺陷 :) 我会继续测试它并重新审视其他解决方案(如果有的话)。
猜你喜欢
  • 1970-01-01
  • 2012-09-08
  • 1970-01-01
  • 2019-04-28
  • 2021-03-04
  • 2020-12-16
  • 2012-01-19
  • 2014-04-29
  • 1970-01-01
相关资源
最近更新 更多