在网上做了一次技术面试,我错过了什么?

时间:2014-09-06 04:37:17

标签: c arrays performance

我正准备尽快面试工作,并进行了技术测试。我对这些问题做得很好,除了这个......

问题的前提是:给定一个数组,找到大小" n"的数组的连续子集的最大差异。实施例

input = [6,8,4,5,3,1,7], n=3
[6,8,4] = the biggest diff = 4 (8-4)
[8,4,5] = the biggest diff = 4 (8-4)
[4,5,3] = 2
[5,3,1] = 4
[3,1,7] = 6
Final return from function:6

输入的限制是这样的:数组的长度将小于100k,n将小于数组的长度。 该功能必须在2秒内完成。

我最初在python中写过这个,但是只收到3/6个正确的测试用例,3个由于时间限制而失败,所以我在C中重写了希望获得更好的性能。

int i,j;
int maxdiff = 0;
int localmax,localmin,localdiff;
for (i=0;i<v_length-d+1;i++){
    localmax = v[i];
    localmin = v[i];
    localdiff = 0;
    for(j=0;j<d;j++){
        if(v[j+i] > localmax){
            localmax = v[j+i];
        }
        if(v[j+i] < localmin){
            localmin = v[j+i];
        }
    }
    localdiff = localmax-localmin;
    if(localdiff > maxdiff){
        maxdiff = localdiff;
    }
}
return maxdiff;

我试过运行这个,但结果相同。 3/6正确,3/6由于运行时失败。

我在这里遗漏了什么吗?我意识到我循环遍历ArraySize-n数组中的每个值,我可以在某种程度上想象我可能只能循环遍历数组一次,但似乎无法弄清楚如何。 有什么建议? 谢谢!

3 个答案:

答案 0 :(得分:3)

你可以在O(nlogn)中使用一个最小堆和一个最大堆来完成子集。 在遍历数组期间从堆中移除第一个元素并添加新元素

答案 1 :(得分:3)

解决涉及长输入序列的连续子序列的问题的最简单方法是使用(单端)队列,至少在概念上。 (如果输入是向量而不是流,则实际上并不需要存储队列,但如果队列是显式的,则算法通常更清晰。)

在这种情况下,解决方案需要找到队列的最大值和最小值之间的最大差异。如果我们需要O(1)解决方案,我们需要一个队列,其操作push_backpop_frontminmax都是O(1)

首先要注意的是,如果我们正在寻找具有该属性的堆栈(其中pop_front被替换为pop_back),那么解决方案是微不足道的:每当我们推出一个新值,我们计算新的最小值和最大值,并将它们与新值一起推送。当我们弹出一个值时,我们也会弹出相关的最小值和最大值,并且堆栈顶部剩余的最小值和最大值再次正确。

我们如何将其转换为队列?答案是我们需要使用堆栈来实现队列。或者更确切地说,使用两个堆栈。这就是所谓的&#34;银行家队列&#34;,它使用两个(功能)堆栈提供摊销的O(1)(功能)队列。

这是一个简单的技巧:其中一个堆栈 - 前堆栈 - 用于推送,另一个堆栈 - 后堆栈 - 用于弹出。队列的前端保留在前端堆栈中,队列的后端在后端堆栈上保持相反的顺序,因此后端堆栈的顶部是队列中的第一个元素。这很好,直到后栈是空的,我们需要弹出第一个元素。此时,我们只需一次从前端堆栈中弹出元素,然后将每个元素推入后堆栈。一旦我们完成了这个操作,前端堆栈就是空的,后端堆栈的顶部是前端堆栈中的最后一个元素,它是队列中的第一个元素,根据需要。

很明显,上面的实现是摊销O(1),因为每个元素都被推到每个堆栈上(并从每个堆栈中弹出一次)。当然,每次操作都不是O(1):每隔一段时间pop_front需要花费很长时间。但平均值总是合适的。 (另一方面,推动总是O(1)。)

因此,我们可以使用两个最小 - 最大堆栈制作最小 - 最大队列,并使用它来解决最大范围问题。

通过该大纲,很容易找到一些优化。首先,我们可以将两个堆栈存储在同一个数组中,如果我们将后堆栈向后存储并与前堆栈连续,那么我们只需要跟踪两个堆栈之间边界的位置,以及弹出的操作前堆栈并将该值推到后堆栈上只需移动边界指针即可。 (在min-max堆栈的情况下,我们需要计算分钟和最大值。)这导致了一个简单的循环缓冲区实现,这是一个常见的队列解决方案。

此外,在移动窗口的情况下,队列的大小是已知的,因此我们不必处理动态调整大小。并且,在输入是矢量的情况下,我们不需要实际将元素推到前面的堆栈上,因为元素位于输入中的已知位置并且我们不需要堆栈最小值前堆栈中的/ max值。

我希望所有这些都足以解释这个C ++实现:

#include <algorithm>
#include <vector>

using std::min;
using std::max;

struct minmax { int min; int max; };

int maxrange(const std::vector<int>& v, int n) {
  int sz = v.size();
  n = min(n, sz);
  if (n <= 1) return 0;
  // The stack only needs n - 2 elements. So this could be adjusted.
  minmax* stack = new minmax[n];
  int loback, hiback, lofront, hifront;
  int maxrange = 0;
  for (int s = n - 1, m = 0; s < sz; ++s, --m) {
    if (m == 0) {
      lofront = hifront = v[s];
      loback = hiback = v[s - 1];
      for (int i = 2; i < n; ++i) {
        stack[i - 2] = minmax{loback, hiback};
        loback = min(loback, v[s - i]);
        hiback = max(hiback, v[s - i]);
      }
      m = n - 1;
    } else {
      lofront = min(lofront, v[s]);
      hifront = max(hifront, v[s]);
      loback = stack[m-1].min;
      hiback = stack[m-1].max;
    }
    maxrange = max(maxrange, max(hifront, hiback) - min(lofront, loback));
  }
  delete[] stack;
  return maxrange;
}

答案 2 :(得分:0)

输入:

int inp[]={...},n=...;
  1. 为输入创建int索引数组

    const int N=sizeof(inp)/sizeof(inp[0]);
    int ix[N];
    for (int i=0;i<N;i++) ix[i]=i;
    
  2. inp[N]升序中的索引排序ix[N](保持inp[N]不变)

    • 因此任何i={0,1,2,...,N-2}都是(inp[ix[i]]<=inp[ix[i+1]])==true
    • 这可以在O(N*log(N))
    • 中完成
  3. 现在从1<n<=N开始的任何大小为int i0=0,1,2,...N-n的子集都是这样完成的:

    int i1=i0+n-1; // valid indexes for subset are i0<=i<=i1;
    int min=0,max=0;
    for (i=  0;i< N;i++) if ((ix[i]>=i0)&&(ix[i]<=i1)) { min=inp[ix[i]]; break; }
    for (i=N-1;i>=0;i--) if ((ix[i]>=i0)&&(ix[i]<=i1)) { max=inp[ix[i]]; break; }
    int diff=max-min;
    
  4. 将子弹3循环到所有位置

    • 找到最大差异......
  5. [注释]

    • 对于较大的n,速度会更快,而对于较小的n
    • 则会更慢
    • 所以最好的方法是根据n size treshold
    • 使用你的方法