计算有界切片的可靠性

时间:2014-01-21 07:22:44

标签: algorithm

我最近参加了编纂的编程测试,问题是在数组中找到有界切片的数量。

我只是简单地向你解释这个问题。

如果Max(SliceArray)-Min(SliceArray)< = K,那么一个数组的片段被称为有界切片。

如果Array [3,5,6,7,3]和K = 2提供..有界切片的数量是9,

数组中的第一个切片(0,0)Min(0,0)= 3 Max(0,0)= 3 Max-Min< = K结果0< = 2所以它是有界切片

数组中的第二个切片(0,1)Min(0,1)= 3 Max(0,1)= 5 Max-Min< = K结果2< = 2所以它是有界切片

阵列中的第二个切片(0,2)Min(0,1)= 3 Max(0,2)= 6 Max-Min< = K result 3< = 2所以它不是有界切片

通过这种方式你可以发现有九个有界切片。

(0,0),(0,1),(1,1),(1,2),(1,3),(2,2),(2,3),(3,3) ,(4,4)。

以下是我提供的解决方案

private int FindBoundSlice(int K, int[] A)
{
    int BoundSlice=0;
    Stack<int> MinStack = new Stack<int>();
    Stack<int> MaxStack = new Stack<int>();




    for (int p = 0; p < A.Length; p++)
    {
        MinStack.Push(A[p]);
        MaxStack.Push(A[p]);
        for (int q = p; q < A.Length; q++)
        {
            if (IsPairBoundedSlice(K, A[p], A[q], MinStack, MaxStack))
                BoundSlice++;
            else
                break;
        }
    }

    return BoundSlice;
}

private bool IsPairBoundedSlice(int K, int P, int Q,Stack<int> Min,Stack<int> Max)
{
    if (Min.Peek() > P)
    {
        Min.Pop();
        Min.Push(P);
    }

    if (Min.Peek() > Q)
    {
        Min.Pop();
        Min.Push(Q);
    }

    if (Max.Peek() < P)
    {
        Max.Pop();
        Max.Push(P);
    }

    if (Max.Peek() < Q)
    {
        Max.Pop();
        Max.Push(Q);
    }

    if (Max.Peek() - Min.Peek() <= K)
        return true;
    else
        return false;
}

但是根据编制审查,上述解决方案在O(N ^ 2)中运行,任何人都可以帮助我找到在O(N)中运行的解决方案。

最大时间复杂度允许O(N)。 最大空间复杂度允许O(N)。

7 个答案:

答案 0 :(得分:1)

最后一个代码根据下面提到的想法工作。这输出9。 (代码用C ++编写。您可以将其更改为Java)

#include <iostream>

using namespace std;

int main()
{
    int A[] = {3,5,6,7,3};
    int K = 2;

    int i = 0;
    int j = 0;
    int minValue = A[0];
    int maxValue = A[0];
    int minIndex = 0;
    int maxIndex = 0;
    int length = sizeof(A)/sizeof(int);
    int count = 0;
    bool stop = false;
    int prevJ = 0;

    while ( (i < length || j < length) && !stop ) {
        if ( maxValue - minValue <= K ) {
            if ( j < length-1 ) {
                j++;
                if ( A[j] > maxValue ) {
                    maxValue = A[j];
                    maxIndex = j;
                }
                if ( A[j] < minValue ) {
                    minValue = A[j];
                    minIndex = j;
                }
            } else {
                count += j - i + 1;
                stop = true;
            }
        } else {
            if ( j > 0 ) {
                int range = j - i;
                int count1 = range * (range + 1) / 2; // Choose 2 from range with repitition.
                int rangeRep = prevJ - i; // We have to subtract already counted ones.
                int count2 = rangeRep * (rangeRep + 1) / 2;
                count += count1 - count2;
                prevJ = j;
            }
            if ( A[j] == minValue ) {

                // first reach the first maxima
                while ( A[i] - minValue <= K )
                    i++;

                // then come down to correct level.
                while ( A[i] - minValue > K )
                    i++;
                maxValue = A[i];
            } else {//if ( A[j] == maxValue ) {
                while ( maxValue - A[i] <= K )
                    i++;
                while ( maxValue - A[i] > K )
                    i++;
                minValue = A[i];
            }
        }
    }
    cout << count << endl;
    return 0;
}

算法(在代码中完成小调整):

Keep two pointers i & j and maintain two values minValue and maxValue..  
1. Initialize i = 0, j = 0, and minValue = maxValue = A[0];   
2. If maxValue - minValue <= K,   
   - Increment count.  
   - Increment j. 
   - if new A[j] > maxValue, maxValue = A[j].  
   - if new A[j] < minValue, minValue = A[j].  
3. If maxValue - minValue > K, this can only happen iif 
   - the new A[j] is either maxValue or minValue. 
   - Hence keep incrementing i untill abs(A[j] - A[i]) <= K. 
   - Then update the minValue and maxValue and proceed accordingly.
4. Goto step 2 if ( i < length-1 || j < length-1 )  

答案 1 :(得分:1)

<强>声明

我可以在这里演示来编写一个算法,在最坏的情况下解决你在线性时间中描述的问题,最多访问输入序列的每个元素两次。

这个答案试图推断和描述我能找到的唯一算法,然后快速浏览一下用Clojure编写的实现。我可能也会编写一个Java实现并更新这个答案,但截至目前,该任务仍然是读者的练习。

编辑:我现在添加了一个有效的 Java实现 。请向下滚动到最后。

编辑:注意到PeterDeRivaz提供了一个序列([0 1 2 3 4],k = 2),使算法三次访问某些元素并可能伪造它。我将在稍后的时间更新有关该问题的答案。

除非我监督一些微不足道的事情,否则我几乎无法想象进一步的简化。反馈非常受欢迎。

<子>(我发现你的问题在这里google搜索codility喜欢锻炼,为作业测试有一个准备自己,当我给自己留出半小时来解决它,没&#39;吨想出了一个解决方案,所以我不高兴并且花了一些专门的吊床时间 - 现在我已经参加了测试,我必须说,发现所提出的练习比这个问题要困难得多。

<强>观察

有关的size,我们可以说,它是划分成triangular numbersize界子片段与它们各自的边界位于该切片边界内(包括其本身的任何有效界切片)。

实施例。 1:[3 1 2]k=2的有界切片,size为3,因此可以划分为(3*4)/2=6个子切片:

    [3 1 2]      ;; slice 1
  [3 1] [1 2]    ;; slices 2-3
[3]   [1]   [2]  ;; slices 4-6

当然,所有这些切片都是k的有界切片。

如果有两个重叠切片,它们都是k的有界切片但边界不同,则数组中可能的有界子切片数量可以计算为总和这些切片的三角形数量减去它们共享的元素数量的三角形数量。

实施例。 2:[4 3 1]的有界切片[3 1 2]k=2在数组[4 3 1 2]中的边界和重叠方面不同。它们共享有界切片[3 1](注意重叠的有界切片总是共享一个有界切片,否则它们不能重叠)。对于两个切片,三角形数字为6,共享切片的三角形数字为(2*3)/2=3。因此,数组可以分为6+6-3=9个切片:

      [4 3 1]   [3 1 2]         ;; 1-2 the overlapping slices 
  [4 3]  6  [3 1]  6  [1 2]     ;; 3-5 two slices and the overlapping slice
[4]     [3]   3   [1]     [2]   ;; 6-9 single-element slices

作为可观察的,重叠有界切片的三角形是两个三角形元素计数的一部分,因此必须从添加的三角形中减去它,否则将被计数两次。同样,所有计数的切片都是k=2的有界切片。

<强>方法

方法是在输入序列中找到最大可能的有界切片,直到所有元素都被访问过,然后使用上述技术对它们求和。

切片有资格作为最大可能的有界切片之一(在下文中通常称为一个最大可能的有界切片,这不应该意味着 最大的一个,只有其中一个)如果满足以下条件:

  • 有界
  • 它可以与左右两个其他切片共享元素
  • 它不会在不受限制的情况下向左或向右增长 - 意思是:如果可能,必须包含许多元素maximum-minimum=k

这暗示有界切片做的有资格作为最大可能的有界片如果不存在具有多个元件的有界切片即完全围绕此切片中的一

作为目标,我们的算法必须能够从数组中的任何元素开始,并确定包含该元素的一个最大可能的有界切片,并且是唯一包含该元素的元素。然后保证从其外部的起点构造的下一个切片将共享前一个切片的起始元素,否则它将是一个最大可能的有界切片与先前找到的切片一起(现在,根据定义,这是不可能的)。一旦找到该算法,就可以从开始构建这样的最大可能的切片,直到不再剩下任何元素为止。这将保证在最坏的情况下每个元素都被遍历两次。

<强>算法

从第一个元素开始,找到包含所述第一个元素的最大可能的有界切片。将其大小的三角形数字添加到计数器。 找到切片后重复一个元素并重复。减去与前片(实测向后搜索)共享的元素的计数的三角形数,添加其总大小的三角形数(实测值搜索转发向后),直到序列已遍历。重复,直到找到切片后找不到更多元素,返回结果。

实施例。 3:对于带有[4 3 1 2 0]的输入序列k=2,找到有界切片的计数。

  1. 从第一个元素开始,找到最大可能的有界切片: [4 3]count=2overlap=0result=3
  2. 在该切片后继续,找到最大可能的有界切片: [3 1 2]size=3overlap=1result=3-1+6=8
  3. ... [1 2 0]size=3overlap=2result=8-3+6=11
  4. result=11
  5. 流程行为

    在最坏的情况下,该过程在时间和空间上呈线性增长。如上所述,元素最多遍历两次。并且每次搜索最大可能的有界切片时,需要存储一些本地。

    然而,由于数组包含较少的最大可能的有界切片,因此该过程变得非常快。例如,带有[4 4 4 4]的数组k>=0只有一个最大可能的有界切片(数组本身)。数组将遍历一次,并返回其元素计数的三角形数作为正确的结果。请注意这是对最坏情况增长O((n * (n+1)) / 2)的解决方案的补充。虽然它们只有一个最大可能的有界切片达到最坏的情况,但对于这种算法,这样的输入意味着最好的情况(从头到尾一次访问每个元素一次)。

    <强>实施

    实施中最困难的部分是从两个方向上的一个元素扫描中找到最大的有界切片。当我们在一个方向上搜索时,我们会跟踪搜索的最小和最大边界,并查看它们与k的比较。一旦找到一个伸展边界的元素,使maximum-minimum <= k不再存在,我们就完成了这个方向。然后我们搜索另一个方向,但使用向后扫描的最后有效边界作为起始边界。

    例4:在我们成功找到最大有界切片 [4 3 1 2 0]后,我们从第三个元素(1)的数组[4 3]开始。此时我们只知道我们的起始值1是最小值,最大值(搜索到的最大有界切片)或两者之间。我们向后扫描(独占)并在第二个元素之后停止(作为4 - 1 > k=2)。最后一个有效范围是13。当我们现在向前扫描时,我们使用相同的算法,但使用13作为边界。请注意,即使在此示例中,我们的起始元素是其中一个边界,但情况并非总是如此:使用2而不是3来考虑相同的方案:2或者1将被确定为绑定,因为我们可以在向前扫描时找到0但也可以找到3 - 只有这样才能确定23中的哪一个(defn scan-while-around "Count numbers in `coll` until a number doesn't pass an (inclusive) interval filter where said interval is guaranteed to contain `around` and grows with each number to a maximum size of `size`. Return count and the lower and upper bounds (inclusive) that were not passed as [count lower upper]." ([around size coll] (scan-while-around around around size coll)) ([lower upper size coll] (letfn [(step [[count lower upper :as result] elem] (let [lower (min lower elem) upper (max upper elem)] (if (<= (- upper lower) size) [(inc count) lower upper] (reduced result))))] (reduce step [0 lower upper] coll)))) 是下限或上限。

    要解决这个问题,这里有一个特殊的计数算法。如果您还不了解Clojure,请不要担心,它的确如此。

    around

    使用此函数,我们可以向前搜索,然后将起始元素作为k传递给我们的起始元素,并使用size作为lower。< / p>

    然后我们从具有相同功能的起始元素开始正向扫描,方法是将先前返回的边界uppertriangular传递给它。

    我们将返回的计数添加到找到的最大可能幻灯片的总计数中,并使用向后扫描的计数作为重叠的长度并减去其三角数。

    请注意,在任何情况下,前向扫描都保证返回至少一个的计数。这对于算法很重要,原因有两个:

    • 我们使用正向扫描的结果计数来确定下一次搜索的起始点(并且在它为0时无限循环)
    • 算法不正确,因为任何起始元素的最小可能最大可能的有界切片总是作为包含起始元素的大小为1的数组存在。

    假设(defn bounded-slice-linear "Linear implementation" [s k] (loop [start-index 0 acc 0] (if (< start-index (count s)) (let [start-elem (nth s start-index) [backw lower upper] (scan-while-around start-elem k (rseq (subvec s 0 start-index))) [forw _ _] (scan-while-around lower upper k (subvec s start-index))] (recur (+ start-index forw) (-> acc (+ (triangular (+ forw backw))) (- (triangular backw))))) acc))) 是返回三角数的函数,这里是最终算法:

    (defn bounded-slice-triangular
      "O(n*(n+1)/2) implementation for testing."
      [s k]
      (reduce (fn [c [elem :as elems]]
                (+ c (first (scan-while-around elem k elems))))
              0
              (take-while seq
                          (iterate #(subvec % 1) s))))
    

    (注意,子向量和其反向序列的创建发生在恒定的时间和与该输入向量得到的载体占有率结构,从而没有&#34;其余尺寸&#34;根据分配正在发生的事情(虽然它可能看起来喜欢它。这是Clojure的一个很好的方面,你可以避免大量的索引摆弄,通常直接使用元素。)

    这是一个用于比较的三角形实现:

    public class BoundedSlices {
        private static int triangular (int i) {
            return ((i * (i+1)) / 2);
        }
        public static int solve (int[] a, int k) {
            int i = 0;
            int result = 0;
    
            while (i < a.length) {
                int lower = a[i];
                int upper = a[i];
                int countBackw = 0;
                int countForw = 0;
    
                for (int j = (i-1); j >= 0; --j) {
                    if (a[j] < lower) {
                        if (upper - a[j] > k)
                            break;
                        else
                            lower = a[j];
                    }
                    else if (a[j] > upper) {
                        if (a[j] - lower > k)
                            break;
                        else
                            upper = a[j];
                    }
                    countBackw++;
                }
    
                for (int j = i; j <a.length; j++) {
                    if (a[j] < lower) {
                        if (upper - a[j] > k)
                            break;
                        else
                            lower = a[j];
                    }
                    else if (a[j] > upper) {
                        if (a[j] - lower > k)
                            break;
                        else
                            upper = a[j];
                    }
                    countForw++;
                }
                result -= triangular(countBackw);
                result += triangular(countForw + countBackw);
                i+= countForw;
            }
            return result;
        }
    }
    

    两个函数仅接受向量作为输入。

    我已经使用各种策略广泛测试了他们的行为是否正确。无论如何,请尽量证明他们是错的。以下是一个完整文件的链接:https://www.refheap.com/32229

    这是用Java实现的算法(没有经过广泛测试,但似乎有用,Java不是我的第一语言。我很乐意学习反馈)

    {{1}}

答案 2 :(得分:0)

以下是我尝试解决此问题的方法:

- you start with p and q form position 0, min =max =0;
- loop until p = q = N-1
- as long as max-min<=k advance q and increment number of bounded slides.
- if max-min >k advance p
- you need to keep track of 2x min/max values because when you advance p, you might remove one  or both of the min/max values
- each time you advance p or q update min/max

如果你愿意,我可以编写代码,但我认为这个想法足够明确......

希望它有所帮助。

答案 3 :(得分:0)

HINTS

其他人已经解释了基本算法,即保持2个指针并根据最大值和最小值之间的当前差异推进开始或结束。

移动结束时很容易更新最大值和最小值。

然而,这个问题的主要挑战是如何在移动开始时进行更新。大多数堆或平衡树结构将花费O(logn)来更新,并且将导致整体O(nlogn)复杂度太高。

在时间O(n)中执行此操作:

  1. 提前结束,直至超过允许的阈值
  2. 然后从这个关键位置向后循环,在当前结束和当前开始之间的每个位置存储阵列中的累积值,以获得最小值和最大值
  3. 您现在可以提前启动指针并立即从数组中查找更新的最小/最大值
  4. 您可以继续使用这些阵列更新开始,直到开始到达关键位置。此时返回步骤1并生成一组新的查找值。
  5. 总的来说,这个过程只会在每个元素上倒退一次,因此总复杂度为O(n)。

    实施例

    对于K为4的序列:

    4,1,2,3,4,5,6,10,12
    

    步骤1推进结束,直到超出界限

    start,4,1,2,3,4,5,end,6,10,12
    

    步骤2从结束向后工作以开始计算数组MAX和MIN。 MAX [i]是从i到end的所有元素的最大值

    Data = start,4,1,2,3,4,5,end,6,10,12
    MAX  = start,5,5,5,5,5,5,critical point=end -
    MIN  = start,1,1,2,3,4,5,critical point=end -
    

    步骤3现在可以提前开始并立即查找范围从开始到临界点的最小值和最小值。

    这些可以与范围临界点的最大值/最小值结合,以查找范围开始到结束的总体最大值/分钟。

    PYTHON CODE

    def count_bounded_slices(A,k):
        if len(A)==0:
            return 0
        t=0
        inf = max(abs(a) for a in A)
        left=0
        right=0
        left_lows = [inf]*len(A)
        left_highs = [-inf]*len(A)
        critical = 0
        right_low = inf
        right_high = -inf
        # Loop invariant
        #  t counts number of bounded slices A[a:b] with a<left
        #  left_lows[i] is defined for values in range(left,critical)
        #    and contains the min of A[left:critical]
        #  left_highs[i] contains the max of A[left:critical]
        #  right_low is the minimum of A[critical:right]
        #  right_high is the maximum of A[critical:right]
        while left<len(A):
            # Extend right as far as possible
            while right<len(A) and max(left_highs[left],max(right_high,A[right]))-min(left_lows[left],min(right_low,A[right]))<=k:
                right_low = min(right_low,A[right])
                right_high = max(right_high,A[right])
                right+=1    
            # Now we know that any slice starting at left and ending before right will satisfy the constraints
            t += right-left
            # If we are at the critical position we need to extend our left arrays
            if left==critical:
                critical=right
                left_low = inf
                left_high = -inf
                for x in range(critical-1,left,-1):
                    left_low = min(left_low,A[x])
                    left_high = max(left_high,A[x])
                    left_lows[x] = left_low
                    left_highs[x] = left_high
                right_low = inf
                right_high = -inf
            left+=1
        return t
    
    A = [3,5,6,7,3]
    print count_bounded_slices(A,2)
    

答案 4 :(得分:0)

我在不同的SO Question中为同一个问题提供了答案
(1)对于A [n]输入,肯定你会有n个切片,所以首先添加。 例如{3,5,4,7,6,3}你肯定会有(0,0)(1,1)(2,2)(3,3)(4,4)(5,5) )。
(2)然后根据最大最大比较找到P和Q.
(3)应用算术级数公式,找出(Q-P)之间的组合数作为X.然后它将是X(X + 1)/ 2
但我们已经考虑过“n”,所以公式将是(x(x + 1)/ 2) - x),即x(x-1) / 2基本算术后。
例如在上面的例子中,如果P是0(3)而Q是3(7),我们有Q-P是3。当应用公式时,该值将为3(3-1)/ 2 = 3.现在添加6(长度)+3。

然后处理Q-min或Q-max记录。

然后检查Min和Max索引。在这种情况下,Min为0 Max为3(显然任何一个都与currentIndex匹配(曾经用过循环)。这里我们照顾(0 ,1)(0,2)(1,2)但我们没有处理(1,3)(2,3)。而不是从索引1开始空洞过程,保存这个数字(位置2,3 = 2),然后从currentindex开始相同的过程(假设min和max为A [currentIndex],就像我们在开始时一样)。最后将数字乘以保留。在我们的例子中2 * 2(A [7],A [6])

它在O(N)时间内以O(N)空间运行。

答案 5 :(得分:0)

我在Scala中提出了一个解决方案:

package test
import scala.collection.mutable.Queue

object BoundedSlice {

  def apply(k:Int, a:Array[Int]):Int = {
    var c = 0
    var q:Queue[Int] = Queue()
    a.map(i => {           
      if(!q.isEmpty && Math.abs(i-q.last) > k) 
        q.clear
      else
        q = q.dropWhile(j => (Math.abs(i-j) > k)).toQueue      
      q += i
      c += q.length
    })
    c
  }

  def main(args: Array[String]): Unit = {
    val a = Array[Int](3,5,6,7,3)
    println(BoundedSlice(2, a))
  }
}

答案 6 :(得分:0)

现在,鳕鱼在O(N)的时间和空间中释放出他们的黄金解决方案。 https://codility.com/media/train/solution-count-bounded-slices.pdf

如果您在阅读pdf之后仍然感到困惑,就像我一样..这里是一个 very nice explanation

pdf的解决方案:

import java.io.Serializable;
import java.util.ArrayList;

public class CellBlock implements Serializable{

private String name;
private String type;
private int capacity;
private int occupancy = 0;
private ArrayList<Cell> cells;

CellBlock(int cap, String nName, String nType, int cellCapacity){
    name = nName;
    type = nType;
    capacity = cap;
    cells = new ArrayList<>(capacity);

    if (type == "Maximum Security" || type == "Minimum Security") {
        for (int i = 0; i < capacity; i++)
            cells.add(new Cell(cellCapacity));
    }
    else if(type == "Infirmary"){
        for (int i = 0; i < capacity; i++)
            cells.add(new InfirmaryCell(cellCapacity));
    }
    else if(type == "Isolation"){
        for (int i = 0; i < capacity; i++)
            cells.add(new IsolationCell(cellCapacity));
    }
}

public void addInmate(int cell, int inmateIdNum){
    cells.get(cell-1).add(inmateIdNum);
    occupancy++;
}

public void addInmate(int cell, int inmateIdNum, String reason){
    cells.get(cell-1).add(inmateIdNum, reason);
    occupancy++;
}

public void addInmate(int cell, int inmateIdNum, int duration){
    cells.get(cell-1).add(inmateIdNum, duration);
    occupancy++;
}

public void removeInmate(int inmateIdNum){
    //search for inmate and remove from list
}

public ArrayList<Cell> getInmates(){ return cells; }

public boolean checkCapacity(){
    if (capacity > occupancy)
        return true;

    return false;
}

public ArrayList<Cell> getCells(){ return cells; }

public ArrayList<String> getOpenCells(){
    ArrayList<String> openCells = new ArrayList<>();

    for (int i = 0; i < cells.size();i++){
        if (cells.get(i).getEmptyBunks() > 0)
            openCells.add(Integer.toString(i+1));
    }

    return openCells;
}
}