【问题标题】:Given the array A and the number K create array B where B[i] = min(A[i], A[i+1].. A[i + K - 1]) [closed]给定数组 A 和数字 K 创建数组 B 其中 B[i] = min(A[i], A[i+1].. A[i + K - 1]) [关闭]
【发布时间】:2014-12-29 07:30:12
【问题描述】:

给定一个整数数组A和一个整数值K,创建数组B,其中B[i]是子数组A[i], A[i+1], ..., A[i+K-1]中的最小值。请注意,B.length 将等于 A.length - K

例如对于K = 3A=[1,2,3,4,0,1,2],解决方案是B=[1,2,0,0,0]

A  =  [1,2,3,4,0,1,2]
       _____| | | | |      
B[1] = 1      | | | | 
         _____| | | | 
B[2] =   2      | | | 
           _____| | | 
B[3] =         0  | | 
             _____| | 
B[4] =         0    | 
               _____| 
B[5] =         0

O(kn)时间复杂度的解决方案如下:

public static int[] createArray(int[] arr, int k) {
    int[] result = new int[arr.length];
    for (int i = 0; i <= arr.length - k; i++) {
        int curSmallestVal = arr[i];
        for (int j = i + 1; j < i + k; j++) {
            curSmallestVal = Math.min(curSmallestVal, arr[j]);
        }
        result[i] = curSmallestVal;
    }

    return result;
}

你能提供一个更优雅的 O(n) 运行时解决方案吗? (可能使用队列)

使用 O(n) 解决方案更新:

public static int[] getMinSlidingWindow(int[] arr, int k) {
    int[] result = new int[arr.length-k+1];
    Deque<Integer> queue = new LinkedList<Integer>();
    //initialize sliding window
    for (int i = 0; i < k; i++) {
        if (!queue.isEmpty() && arr[queue.getLast()] >= arr[i])
            queue.removeLast();
        queue.addLast(i);
    }

    for (int i = k; i < arr.length; i++) {
        result[i-k] = arr[queue.getFirst()];
        while (!queue.isEmpty() && arr[queue.getLast()] >= arr[i])
            queue.removeLast();
        queue.addLast(i);
        while (!queue.isEmpty() && queue.getFirst() <= i-k)
            queue.removeFirst();
    }

    result[arr.length-k] = arr[queue.removeFirst()]; 

    return result;
}

【问题讨论】:

  • 我不明白这个问题。你能提供一个更好的例子吗?什么是 K,它来自哪里?
  • 示例改进。
  • 谢谢你,Gergely Orosz。单词不好用的时候最好用数学方程。
  • 这个问题更适合codereview

标签: arrays algorithm deque


【解决方案1】:

使用带有一些额外逻辑的双端队列(支持从前面和后面添加推送和弹出的队列),您可以构建一个运行 O(n) 的解决方案。

这是解决方案的伪代码。

void getMaxSlidingWindow(int[] A, int k) {
    int[] B = new int[A.length - k];
    // Store the indexes of A in Q
    // Q.front(): index of smallest element in the window, Q.back(): index of the largest one in the window
    DobuleEndedQueue<int> Q = new DobuleEndedQueue<int>(); 

    for(int i=0; i<k; i++) {
       // Fill up the double ended queue for the first k elements
       // Remove elements that we would ignore because they're bigger than the next one in the window
       while(!Q.empty() && A[i] <= A[Q.back()]) {
          Q.popBack();
       }
       Q.pushBack(i);
    }

     for(int i=k; i < A.length; i++) {
       B[i - k] = A[Q.front()]; // The first element in the queue is the index of the smallest element in the window
       // Add the current element to the queue. Before we do, remove all elements that we would ignore immediately because they're bigger than the current one
       while(!Q.empty() && A[i] <= A[Q.back()]  ) {
          Q.popBack();
       }
       Q.pushToBack(i);
       // Remove any index from the front of the queue which is no longer in the window
       while(!Q.empty() && Q.front() <= i-k) {
         Q.popFront();
       }
    }
    B[A.length - k] = A[Q.front()];
}

此解决方案的时间复杂度为 O(n):我们遍历所有元素一次,然后将它们添加或删除一次到双端队列。完成的最大操作是 2n,这是一个 O(n) 复杂度。

要使此解决方案起作用,您需要使用以下操作实现双端队列数据结构:

class DobuleEndedQueue
int front(), void pushFront(int n), void popFront() // peeks at the front element, adds and removes to the front
int back(), void pushBack(int n) , void popBack() // same for the back element

用一个简单的例子进一步解释算法:

  • 遍历前 k 个元素并将它们作为索引插入到双端 Q 数据结构中,这样 A[Q.front()] 是最小元素,A[Q.back()] 是最大元素窗口。
  • 在我们构建窗口时,从该队列中丢弃“不必要的”元素:不会被计算在内的最小元素和窗口外的元素
  • 为 A=[8, 6, 9, 2] 和 k-3 构建队列的示例
    1. Q = [0],(在 Q 后面插入 0,因为 A[0] 是我们见过的最小元素。)
    1. Q = [1], (A[1]
    1. Q = [1, 2], B=[8] (A[2] is > Q.back(), 所以我们只需在 Q 上加 2。B[0] 将是 Q 中的最小项,即A[Q.first()],即A[1])
    1. Q = [3], B=[8, 2] (A[4] 小于 Q 中的所有元素,所以我们将它们全部弹出。B[1] 将是 Q 中的最小项,A[ Q.first()],即A[3]

【讨论】:

  • 你算法的时间复杂度是多少??
  • 答案中详细说明它是 O(n)
【解决方案2】:

使用带有双端队列的标准滑动窗口最小算法可以实现O(n) 时间复杂度。这是它的详细描述:http://people.cs.uct.ac.za/~ksmith/articles/sliding_window_minimum.html

【讨论】:

  • 我认为你应该在页面中包含一个对算法的简要说明,以便将来如果链接无效,其他人将从你的解释中获取信息
  • 不过,他们说的是摊销O(n),这意味着在最坏的情况下它仍然可以是O(kn)
  • @MujtabaHasan 没有。它总是O(n)。它永远不是O(nk)
  • @VikramBhat 这个算法是众所周知的。它的名字足以了解它的更多信息。该链接只是为了方便。
  • 我从那个页面引用它“懒惰地删除元素的想法是一个突出的想法,但是通过在将元素插入窗口时付出更多的努力,我们可以得到 amortized O( 1) 运行时".. 要保持列表排序并删除任何大于当前元素的元素,他们必须从后面删除所有大于它的元素。请仔细检查..
【解决方案3】:

我想到的第一个想法是使用两组。两组都存储std::pair&lt;index_t,value_t&gt;,但顺序不同。 (一个按索引排序,一个按值排序)。这样,您可以在每一步迭代槽数组找到最小值(集合中的第一个元素按值排序)以及要从两个集合中删除的元素/对(集合中的第一个元素按索引排序)。在每个步骤中,您在每组中添加对并从每组中删除对。

【讨论】:

  • 它的时间复杂度为O(n log n),而不是O(n)
  • 更准确地说是 O (n * log(k) )
猜你喜欢
  • 2020-06-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-09
  • 2018-09-16
  • 1970-01-01
  • 2021-11-01
  • 1970-01-01
相关资源
最近更新 更多