10

A sequence of numbers was given in an interview such that A[0] >= A[1] and A[N-1] >= A[N-2]. I was asked to find at-least one triplet such that A[n-1] >= A[n] <= A[n+1].

I tried to solve in iterations. Interviewer expected better than linear time solution. How should I approach this question?

Example: 9 8 5 4 3 2 6 7

Answer: 3 2 6

Jayram
  • 18,820
  • 6
  • 51
  • 68
  • 2
    What if there isn't one, also why would you need to do a n^3 solution, this seems easy to do in linear – aaronman Jul 12 '13 at 04:04
  • 1
    You have not given a clear description of what you know about the input. What's `N` in `A[N-1] >= A[N-2]`? The length of the sequence? Any index in some range? – user2357112 Jul 12 '13 at 04:05
  • 1
    I just understood the question, it's interesting but I think you should try to explain it better – aaronman Jul 12 '13 at 04:18
  • The edit doesn't make sense. You tried to solve it in... iterations? I suppose that's technically a valid sentence, but it's analogous to "I tried to use a loop somewhere". – user2357112 Jul 12 '13 at 04:27
  • @user2357112 I can solve it in linear time how come google doesn't wanna interview me? – aaronman Jul 12 '13 at 04:28
  • 1
    http://stackoverflow.com/questions/16887279/divide-and-conquer-algorithm-applied-in-finding-a-peak-in-an-array – SomeWittyUsername Jul 12 '13 at 04:33
  • 1
    I think the reason people were confused at first is because there is no need to find the trilpet, because the triplet will always be at the min point so you can just return that – aaronman Jul 12 '13 at 04:40
  • @aaronman By the question as stated there might be multiple local mimima - you actually only need to find one. – Jeff Jul 12 '13 at 04:56

3 Answers3

9

We can solve this in O(logn) time using divide & conquer aka. binary search. Better than linear time. So we need to find a triplet such that A[n-1] >= A[n] <= A[n+1].

First find the mid of the given array. If mid is smaller than its left and greater than its right. then return, thats your answer. Incidentally this would be a basecase in your recursion. Also if len(arr) < 3 then too return. another basecase.

Now comes the recursion scenarios. When to recurse, we would need to inspect further right. For that, If mid is greater than the element on its left then consider start to left of the array as a subproblem and recurse with this new array. i.e. in tangible terms at this point we would have ...26... with index n being 6. So we move left to see if the element to the left of 2 completes the triplet.

Otherwise if mid is greater than element on its right subarray then consider mid+1 to right of the array as a subproblem and recurse.


More Theory: The above should be sufficient to understand the problem but read on. The problem essentially boils down to finding local minima in a given set of elements. A number in the array is called local minima if it is smaller than both its left and right numbers which precisely boils down to A[n-1] >= A[n] <= A[n+1].

A given array such that its first 2 elements are decreasing and last 2 elements are increasing HAS to have a local minima. Why is that? Lets prove this by negation. If first two numbers are decreasing, and there is no local minima, that means 3rd number is less than 2nd number. otherwise 2nd number would have been local minima. Following the same logic 4th number will have to be less than 3rd number and so on and so forth. So the numbers in the array will have to be in decreasing order. Which violates the constraint of last two numbers being in increasing order. This proves by negation that there need to be a local minima.

The above theory suggests a O(n) linear approach but we definitely can do better. But the theory definitely gives us a different perspective about the problem.


Code: Here's python code (fyi - was typed in stackoverflow text editor freehand, it might misbheave).

def local_minima(arr, start, end):
    mid = (start+end)/2

    if mid-2 < 0 and mid+1 >= len(arr):
        return -1;

    if arr[mid-2] > arr[mid-1] and arr[mid-1] < arr[mid]: #found it!
        return mid-1;

    if arr[mid-1] > arr[mid-2]:
        return local_minima(arr, start, mid);
    else:
        return local_minima(arr, mid, end);

Note that I just return the index of the n. To print out the triple just do -1 and +1 to the returned index. source

Srikar Appalaraju
  • 71,928
  • 54
  • 216
  • 264
  • This assumes that `A` is holding values of a smoothly changing function. That crucial information is missing from the OP. – Raedwald Jul 12 '13 at 09:34
  • @Raedwald what do you mean "smoothly changing function"? – Srikar Appalaraju Jul 12 '13 at 10:24
  • I mean that to "find local minima", you are assuming that `arr[i] = f(i)`, with `f(i)` a smooth function of `i`. If `arr` just holds random numbers, for example, no solution better than a linear search is possible. – Raedwald Jul 12 '13 at 12:24
  • 1
    i though about what you said but in case of a non-smooth function and the array has completely random numbers the above binary search method should work. Dont find a reason why it should not work... – Srikar Appalaraju Jul 12 '13 at 14:08
2

It sounds like what you're asking is this:

You have a sequence of numbers. It starts decreasing and continues to decrease until element n, then it starts increasing until the end of the sequence. Find n.

This is a (non-optimal) solution in linear time:

for (i = 1; i < length(A) - 1; i++)
{
    if ((A[i-1] >= A[i]) && (A[i] <= A[i+1]))
        return i;
}

To do better than linear time, you need to use the information that you get from the fact that the series decreases then increases.

Consider the difference between A[i] and A[i+1]. If A[i] > A[i+1], then n > i, since the values are still decreasing. If A[i] <= A[i+1], then n <= i, since the values are now increasing. In this case you need to check the difference between A[i-1] and A[i].

This is a solution in log time:

int boundUpper = length(A) - 1;
int boundLower = 1;
int i = (boundUpper + boundLower) / 2; //initial estimate

while (true)
{
    if (A[i] > A[i+1])
        boundLower = i + 1;
    else if (A[i-1] >= A[i])
        return i;
    else
        boundUpper = i;

    i = (boundLower + boundUpper) / 2;
}

I'll leave it to you to add in the necessary error check in the case that A does not have an element satisfying the criteria.

Brian L
  • 3,201
  • 1
  • 15
  • 15
1

Linear you could just do by iterating through the set, comparing them all.

You could also check the slope of the first two, then do a kind of binary chop/in order traversal comparing pairs until you find one of the opposite slope. That would amortize to a better than n time, I think, though it's not guaranteed.

edit: just realised what your ordering meant. The binary chop method is guaranteed to do this in <n time, as there is guaranteed to be a point of change (assuming that your N-1, N-2 are the last two elements of the list). This means you just need to find it/one of them, in which case binary chop will do it in order log(n)

Jeff
  • 12,555
  • 5
  • 33
  • 60