Counting bounded slice codility
Asked Answered
T

8

13

I have recently attended a programming test in codility, and the question is to find the Number of bounded slice in an array..

I am just giving you breif explanation of the question.

A Slice of an array said to be a Bounded slice if Max(SliceArray)-Min(SliceArray)<=K.

If Array [3,5,6,7,3] and K=2 provided .. the number of bounded slice is 9,

first slice (0,0) in the array Min(0,0)=3 Max(0,0)=3 Max-Min<=K result 0<=2 so it is bounded slice

second slice (0,1) in the array Min(0,1)=3 Max(0,1)=5 Max-Min<=K result 2<=2 so it is bounded slice

second slice (0,2) in the array Min(0,1)=3 Max(0,2)=6 Max-Min<=K result 3<=2 so it is not bounded slice

in this way you can find that there are nine bounded slice.

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

Following is the solution i have provided

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;
}

But as per codility review the above mentioned solution is running in O(N^2), can anybody help me in finding the solution which runs in O(N).

Maximum Time Complexity allowed O(N). Maximum Space Complexity allowed O(N).

Truncated answered 21/1, 2014 at 7:22 Comment(0)
C
1

HINTS

Others have explained the basic algorithm which is to keep 2 pointers and advance the start or the end depending on the current difference between maximum and minimum.

It is easy to update the maximum and minimum when moving the end.

However, the main challenge of this problem is how to update when moving the start. Most heap or balanced tree structures will cost O(logn) to update, and will result in an overall O(nlogn) complexity which is too high.

To do this in time O(n):

  1. Advance the end until you exceed the allowed threshold
  2. Then loop backwards from this critical position storing a cumulative value in an array for the minimum and maximum at every location between the current end and the current start
  3. You can now advance the start pointer and immediately lookup from the arrays the updated min/max values
  4. You can carry on using these arrays to update start until start reaches the critical position. At this point return to step 1 and generate a new set of lookup values.

Overall this procedure will work backwards over every element exactly once, and so the total complexity is O(n).

EXAMPLE

For the sequence with K of 4:

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

Step 1 advances the end until we exceed the bound

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

Step 2 works backwards from end to start computing array MAX and MIN. MAX[i] is maximum of all elements from i to 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 -

Step 3 can now advance start and immediately lookup the smallest values of max and min in the range start to critical point.

These can be combined with the max/min in the range critical point to end to find the overall max/min for the range start to end.

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)
Coccid answered 21/1, 2014 at 10:12 Comment(12)
may be i still not able to grasp your soultion.. kindly tell me how to solve the following sequence 4,1,2,3,4,5,6,5,4 and K=4Truncated
Won't the arrays of MAX and MIN need continuous updates as the end pointer advances.Tufthunter
@AbhishekBansal not quite. You only need to update the arrays when the start reaches the critical point. MAX[i] will give the maximum between i and critical point. To find the overall max, you need to combine this with the max between critical point and end (which you can easily keep track of).Coccid
it looks to me like you might loose some of the slices along tha way with this approach: what can you do for (1,2), (1,3) and so on...? The Min Max arrays don't help and updating them will increase the complexity to O(n(n+1)/2)Pismire
@PeterdeRivaz It would really be great if you could write out your algorithm for the example sequence and describe it in more detail. Unfortunately as it is described now I fail to understand how it is meant (really tried hard (tried to implemented it)) - maybe my bad, but a full example with more steps including how the result is calculated would significantly help.Transpontine
@lgrapenthin I might have a go tonight or tomorrow to actually write the code. Your algorithm looks much better in any case (less space requirement than mine) but I will need to play with it a bit to understand how it works (e.g. for a sequence 1,2,3,4,5).Coccid
@PeterdeRivaz For that sequence with a k=2 my integrity test fails and apparently some elements are visited three times. Good find! (My generated test samples apparently never provided this scenario). I don't know yet whether it falsifies my answer, probably it does :( - At least it makes parts of my explanation regarding two visits incorrect.Transpontine
(I can recreate the problem with any sorted sequence (without gaps > 1) and k=1/2 maximum, not exceeding three visits though.)Transpontine
@lgrapenthin I've added some example code that might help explain the algorithmCoccid
I have taken some time to understand the solution(i have worked on some other thing also, so it took long time).. You are updating Min and Max whenever reaching critical point.. will it not breach O(N).. anyhow i am trying the soultion in c#.. ThanksTruncated
this solution is not O(n) it is O(n+m)Kalidasa
this solution will give you 100% in codility. but the proper solution requires a recursive loop but it will not satisfy their performance criteria. codility.com/demo/results/demoZ8JKWC-3H2 to calculate a dynamic size slice we need to work with 2 pointers. SO i am but puzzled with this question. if someone have a test data for an array that provide 4 element min avg that has a different starting index than the 3 element it would help to test.Kalidasa
B
3

Now codility release their golden solution with O(N) time and space. https://codility.com/media/train/solution-count-bounded-slices.pdf

if you still confused after read the pdf, like me.. here is a very nice explanation

The solution from the pdf:

def boundedSlicesGolden(K, A):
N = len(A)

maxQ = [0] * (N + 1)
posmaxQ = [0] * (N + 1)
minQ = [0] * (N + 1)
posminQ = [0] * (N + 1)

firstMax, lastMax = 0, -1
firstMin, lastMin = 0, -1
j, result = 0, 0

for i in xrange(N):
    while (j < N):
        # added new maximum element
        while (lastMax >= firstMax and maxQ[lastMax] <= A[j]):
            lastMax -= 1
        lastMax += 1
        maxQ[lastMax] = A[j]
        posmaxQ[lastMax] = j

        # added new minimum element
        while (lastMin >= firstMin and minQ[lastMin] >= A[j]):
            lastMin -= 1
        lastMin += 1
        minQ[lastMin] = A[j]
        posminQ[lastMin] = j

        if (maxQ[firstMax] - minQ[firstMin] <= K):
            j += 1
        else:
            break
    result += (j - i)
    if result >= maxINT:
        return maxINT
    if posminQ[firstMin] == i:
        firstMin += 1
    if posmaxQ[firstMax] == i:
        firstMax += 1
return result
Bemused answered 26/10, 2015 at 22:20 Comment(0)
T
2

Disclaimer

It is possible and I demonstrate it here to write an algorithm that solves the problem you described in linear time in the worst case, visiting each element of the input sequence at a maximum of two times.

This answer is an attempt to deduce and describe the only algorithm I could find and then gives a quick tour through an implementation written in Clojure. I will probably write a Java implementation as well and update this answer but as of now that task is left as an excercise to the reader.

EDIT: I have now added a working Java implementation. Please scroll down to the end.

EDIT: Notices that PeterDeRivaz provided a sequence ([0 1 2 3 4], k=2) making the algorithm visit certain elements three times and probably falsifying it. I will update the answer at later time regarding that issue.

Unless I have overseen something trivial I can hardly imagine significant further simplification. Feedback is highly welcome.

(I found your question here when googling for codility like exercises as a preparation for a job test there myself. I set myself aside half an hour to solve it and didn't come up with a solution, so I was unhappy and spent some dedicated hammock time - now that I have taken the test I must say found the presented exercises significantly less difficult than this problem).

Observations

For any valid bounded slice of size we can say that it is divisible into the triangular number of size bounded sub-slices with their individual bounds lying within the slices bounds (including itself).

Ex. 1: [3 1 2] is a bounded slice for k=2, has a size of 3 and thus can be divided into (3*4)/2=6 sub-slices:

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

Naturally, all those slices are bounded slices for k.

When you have two overlapping slices that are both bounded slices for k but differ in their bounds, the amount of possible bounded sub-slices in the array can be calculated as the sum of the triangular numbers of those slices minus the triangular number of the count of elements they share.

Ex. 2: The bounded slices [4 3 1] and [3 1 2] for k=2 differ in bounds and overlap in the array [4 3 1 2]. They share the bounded slice [3 1] (notice that overlapping bounded slices always share a bounded slice, otherwise they could not overlap). For both slices the triangular number is 6, the triangular number of the shared slice is (2*3)/2=3. Thus the array can be divided into 6+6-3=9 slices:

      [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

As observable, the triangle of the overlapping bounded slice is part of both triangles element count, so that is why it must be subtracted from the added triangles as it otherwise would be counted twice. Again, all counted slices are bounded slices for k=2.

Approach

The approach is to find the largest possible bounded slices within the input sequence until all elements have been visited, then to sum them up using the technique described above.

A slice qualifies as one of the largest possible bounded slices (in the following text often referred as one largest possible bounded slice which shall then not mean the largest one, only one of them) if the following conditions are fulfilled:

  • It is bounded
  • It may share elements with two other slices to its left and right
  • It can not grow to the left or to the right without becoming unbounded - meaning: If it is possible, it has to contain so many elements that its maximum-minimum=k

By implication a bounded slice does not qualify as one of the largest possible bounded slices if there is a bounded slice with more elements that entirely encloses this slice

As a goal our algorithm must be capable to start at any element in the array and determine one largest possible bounded slice that contains that element and is the only one to contain it. It is then guaranteed that the next slice constructed from a starting point outside of it will not share the starting element of the previous slice because otherwise it would be one largest possible bounded slice with the previously found slice together (which now, by definition, is impossible). Once that algorithm has been found it can be applied sequentially from the beginning building such largest possible slices until no more elements are left. This would guarantee that each element is traversed two times in the worst case.

Algorithm

Start at the first element and find the largest possible bounded slice that includes said first element. Add the triangular number of its size to the counter. Continue exactly one element after found slice and repeat. Subtract the triangular number of the count of elements shared with the previous slice (found searching backwards), add the triangular number of its total size (found searching forwards and backwards) until the sequence has been traversed. Repeat until no more elements can be found after a found slice, return the result.

Ex. 3: For the input sequence [4 3 1 2 0] with k=2 find the count of bounded slices.

  1. Start at the first element, find the largest possible bounded slice: [4 3], count=2, overlap=0, result=3
  2. Continue after that slice, find the largest possible bounded slice: [3 1 2], size=3, overlap=1, result=3-1+6=8
  3. ... [1 2 0], size=3, overlap=2, result=8-3+6=11
  4. result=11

Process behavior

In the worst case the process grows linearly in time and space. As proven above, elements are traversed two times at max. and per search for a largest possible bounded slice some locals need to be stored.

However, the process becomes dramatically faster as the array contains less largest possible bounded slices. For example, the array [4 4 4 4] with k>=0 has only one largest possible bounded slice (the array itself). The array will be traversed once and the triangular number of the count of its elements is returned as the correct result. Notice how this is complementary to solutions of worst case growth O((n * (n+1)) / 2). While they reach their worst case with only one largest possible bounded slice, for this algorithm such input would mean the best case (one visit per element in one pass from start to end).

Implementation

The most difficult part of the implementation is to find a largest bounded slice from one element scanning in two directions. When we search in one direction, we track the minimum and maximum bounds of our search and see how they compare to k. Once an element has been found that stretches the bounds so that maximum-minimum <= k does not hold anymore, we are done in that direction. Then we search into the other direction but use the last valid bounds of the backwards scan as starting bounds.

Ex.4: We start in the array [4 3 1 2 0] at the third element (1) after we have successfully found the largest bounded slice [4 3]. At this point we only know that our starting value 1 is the minimum, the maximum (of the searched largest bounded slice) or between those two. We scan backwards (exclusive) and stop after the second element (as 4 - 1 > k=2). The last valid bounds were 1 and 3. When we now scan forwards, we use the same algorithm but use 1 and 3 as bounds. Notice that even though in this example our starting element is one of the bounds, that is not always the case: Consider the same scenario with a 2 instead of the 3: Neither that 2 or the 1 would be determined to be a bound as we could find a 0 but also a 3 while scanning forwards - only then it could be decided which of 2 or 3 is a lower or upper bound.

To solve that problem here is a special counting algorithm. Don't worry if you don't understand Clojure yet, it does just what it says.

(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))))

Using this function we can search backwards, from before the starting element passing it our starting element as around and using k as the size.

Then we start a forward scan from the starting element with the same function, by passing it the previously returned bounds lower and upper.

We add their returned counts to the total count of the found largest possible slide and use the count of the backwards scan as the length of the overlap and subtract its triangular number.

Notice that in any case the forward scan is guaranteed to return a count of at least one. This is important for the algorithm for two reasons:

  • We use the resulting count of the forward scan to determine the starting point of the next search (and would loop infinitely with it being 0)
  • The algorithm would not be correct as for any starting element the smallest possible largest possible bounded slice always exists as an array of size 1 containing the starting element.

Assuming that triangular is a function returning the triangular number, here is the final algorithm:

(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)))

(Notice that the creation of subvectors and their reverse sequences happens in constant time and that the resulting vectors share structure with the input vector so no "rest-size" depending allocation is happening (although it may look like it). This is one of the beautiful aspects of Clojure, that you can avoid tons of index-fiddling and usually work with elements directly.)

Here is a triangular implementation for comparison:

(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))))

Both functions only accept vectors as input.

I have extensively tested their behavior for correctness using various strategies. Please try to prove them wrong anyway. Here is a link to a full file to hack on: https://www.refheap.com/32229

Here is the algorithm implemented in Java (not tested as extensively but seems to work, Java is not my first language. I'd be happy about feedback to learn)

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;
    }
}
Transpontine answered 3/2, 2014 at 17:57 Comment(1)
I have tried your solutions, still it gets timeout error for one test case, your algorithm scores 90 out of 100, still it has problems, if you can come up with a solution let me know. Expected running time is 1.02s and yours algorithm running it in >1.21 s, i guess your solution is close.. still not perfect..Truncated
P
1

Here is my attempt at solving this problem:

- 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

I can write the code if you want, but I think the idea is explicit enough...

Hope it helps.

Pismire answered 21/1, 2014 at 9:56 Comment(2)
Thanks for your time.. still i see there is a chance for two for loops in your solution.. below is my understanding of your soultion. If wrong please rectify. for p=0 p<N for q=p q<N if IsArraySliceBounded(p,q) Increase the counter else break the inner loop and continue outer loop I have another doubt.. given array [3,5,6,7,3] and K=10000.. now all the subset of the arrays are bounded slice.. if we have the constrain p<q then there are (n*n+1)/2 subsets, how to provide the solution in O(N).Truncated
no: only one loop while( (p!=q) && (p!=N) && (q!=N) ). Do not use a for loop; it'll be much more difficult to follow the logic. Worst case scenario each element will be visited twice - and that is still linear time.Pismire
T
1

Finally a code that works according to the below mentioned idea. This outputs 9. (The code is in C++. You can change it for 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;
}

Algorithm (minor tweaking done in code):

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 )  
Tufthunter answered 21/1, 2014 at 9:58 Comment(2)
what happens if, when you increment i, you take out the min/max value in (p,q); you would have to go through all the elements of the interval to find the new value. So for the worst case you are looking at O( n(n+1)/2 );Pismire
@Pismire You are right, will need to take into that possibility.Tufthunter
C
1

HINTS

Others have explained the basic algorithm which is to keep 2 pointers and advance the start or the end depending on the current difference between maximum and minimum.

It is easy to update the maximum and minimum when moving the end.

However, the main challenge of this problem is how to update when moving the start. Most heap or balanced tree structures will cost O(logn) to update, and will result in an overall O(nlogn) complexity which is too high.

To do this in time O(n):

  1. Advance the end until you exceed the allowed threshold
  2. Then loop backwards from this critical position storing a cumulative value in an array for the minimum and maximum at every location between the current end and the current start
  3. You can now advance the start pointer and immediately lookup from the arrays the updated min/max values
  4. You can carry on using these arrays to update start until start reaches the critical position. At this point return to step 1 and generate a new set of lookup values.

Overall this procedure will work backwards over every element exactly once, and so the total complexity is O(n).

EXAMPLE

For the sequence with K of 4:

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

Step 1 advances the end until we exceed the bound

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

Step 2 works backwards from end to start computing array MAX and MIN. MAX[i] is maximum of all elements from i to 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 -

Step 3 can now advance start and immediately lookup the smallest values of max and min in the range start to critical point.

These can be combined with the max/min in the range critical point to end to find the overall max/min for the range start to end.

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)
Coccid answered 21/1, 2014 at 10:12 Comment(12)
may be i still not able to grasp your soultion.. kindly tell me how to solve the following sequence 4,1,2,3,4,5,6,5,4 and K=4Truncated
Won't the arrays of MAX and MIN need continuous updates as the end pointer advances.Tufthunter
@AbhishekBansal not quite. You only need to update the arrays when the start reaches the critical point. MAX[i] will give the maximum between i and critical point. To find the overall max, you need to combine this with the max between critical point and end (which you can easily keep track of).Coccid
it looks to me like you might loose some of the slices along tha way with this approach: what can you do for (1,2), (1,3) and so on...? The Min Max arrays don't help and updating them will increase the complexity to O(n(n+1)/2)Pismire
@PeterdeRivaz It would really be great if you could write out your algorithm for the example sequence and describe it in more detail. Unfortunately as it is described now I fail to understand how it is meant (really tried hard (tried to implemented it)) - maybe my bad, but a full example with more steps including how the result is calculated would significantly help.Transpontine
@lgrapenthin I might have a go tonight or tomorrow to actually write the code. Your algorithm looks much better in any case (less space requirement than mine) but I will need to play with it a bit to understand how it works (e.g. for a sequence 1,2,3,4,5).Coccid
@PeterdeRivaz For that sequence with a k=2 my integrity test fails and apparently some elements are visited three times. Good find! (My generated test samples apparently never provided this scenario). I don't know yet whether it falsifies my answer, probably it does :( - At least it makes parts of my explanation regarding two visits incorrect.Transpontine
(I can recreate the problem with any sorted sequence (without gaps > 1) and k=1/2 maximum, not exceeding three visits though.)Transpontine
@lgrapenthin I've added some example code that might help explain the algorithmCoccid
I have taken some time to understand the solution(i have worked on some other thing also, so it took long time).. You are updating Min and Max whenever reaching critical point.. will it not breach O(N).. anyhow i am trying the soultion in c#.. ThanksTruncated
this solution is not O(n) it is O(n+m)Kalidasa
this solution will give you 100% in codility. but the proper solution requires a recursive loop but it will not satisfy their performance criteria. codility.com/demo/results/demoZ8JKWC-3H2 to calculate a dynamic size slice we need to work with 2 pointers. SO i am but puzzled with this question. if someone have a test data for an array that provide 4 element min avg that has a different starting index than the 3 element it would help to test.Kalidasa
P
0

I have provided the answer for the same question in different SO Question
(1) For an A[n] input , for sure you will have n slices , So add at first. for example for {3,5,4,7,6,3} you will have for sure (0,0)(1,1)(2,2)(3,3)(4,4) (5,5).
(2) Then find the P and Q based on min max comparison.
(3) apply the Arithmetic series formula to find the number of combination between (Q-P) as a X . then it would be X ( X+1) /2
But we have considered "n" already so the formula would be (x ( x+1) /2) - x) which is x (x-1) /2 after basic arithmetic.

For example in the above example if P is 0 (3) and Q is 3 (7) we have Q-P is 3 . When apply the formula the value would be 3 (3-1)/2 = 3. Now add the 6 (length) + 3 .

Then take care of Q- min or Q - max records.

Then check the Min and Max index .In this case Min as 0 Max as 3 (obivously any one of the would match with currentIndex (which ever used to loop). here we took care of (0,1)(0,2)(1,2) but we have not taken care of (1,3) (2,3) . Rather than start the hole process from index 1 , save this number (position 2,3 = 2) , then start same process from currentindex( assume min and max as A[currentIndex] as we did while starting). finaly multiply the number with preserved . in our case 2 * 2 ( A[7],A[6]) .

It runs in O(N) time with O(N) space.

Pipestone answered 7/2, 2014 at 21:4 Comment(0)
A
0

I came up with a solution in 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))
  }
}
Aimee answered 8/3, 2014 at 16:12 Comment(0)
E
0

From the observation that if (i, j-1) is a bounded slice, then (i+1, j-1), ..., (j-2, j-1) are also bounded. Then we can use a caterpillar method with a multiset storing the elements [i, j), and we move the tail of the caterpillar by removing A[i] and advancing it by adding A[j]. It's an O(NLogN) algorithm.

Here is the solution:

#include <set>
int solution(int K, vector<int> &A) {
    multiset<int> s;
    const auto query = [&](int i, int j) // both ends inclusive
    {
        int min = *s.begin();
        int max = *s.rbegin();
        return make_pair(min, max);
    };

    int n = A.size();
    int res = 0;
    int j = 0;
    int INT_MAX = 1'000'000'000;
    s.insert(A[0]);

    for(int i=0;i<n;++i) {
        if (i>0)
        {
            auto it = s.find(A[i-1]);
            s.erase(it);
        }
        while(j<n) {
            const auto range = query(i,j);
            if (range.second - range.first <= K)
                j++;
            else
                break;
            if (j<n)
                s.insert(A[j]);
        }
        res += j-i;
        if (res > INT_MAX) return INT_MAX;
    }

    return res;
}
Ernaldus answered 2/6 at 18:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.