33

Given an array of integers, find the local minima. An element A[i] is defined as a local minimum if A[i-1] > A[i] and A[i] < A[i+1] where i = 1...n-2. In case of boundary elements, the number has to be just smaller than its adjacent number.

I know if there is only one local minimum, then we can solve with modified binary search. But if it is known that there exist multiple local minima in the array, can it be solved in O(log n) time?

templatetypedef
  • 362,284
  • 104
  • 897
  • 1,065
devsda
  • 4,112
  • 9
  • 50
  • 87

7 Answers7

68

If the array elements are not guaranteed to be distinct, then it's not possible to do this in O(log n) time. The reason for this is the following: suppose that you have an array where all n > 1 values are the same. In this case, none of the elements can be local minima, because no element is less than its neighbors. However, in order to determine that all values are the same, you will have to look at all the array elements, which takes O(n) time. If you use less than O(n) time, you can't necessarily look at all the array elements.

If, on the other hand, the array elements are guaranteed to be distinct, you can solve this in O(log n) time using the following observations:

  1. If there is just one element, it's guaranteed to be a local minimum.
  2. If there are multiple elements, look at the middle element. If it's a local minimum, you're done. Otherwise, at least one of the elements next to it must be smaller than it. Now, imagine what would happen if you were to start at one of the smaller elements and progressively move toward one of the ends of the array in the direction away from the middle element. At each step, either the next element is smaller than the previous, or it will be bigger. Eventually, you will either hit the end of the array this way, or you will hit a local minimum. Note that this means that you could do this to find a local minimum. However, we're not actually going to do that. Instead, we'll use the fact that a local minimum will exist in this half of the array as a justification for throwing away one half of the array. In what remains, we are guaranteed to find a local minimum.

Consequently, you can build up the following recursive algorithm:

  1. If there is just one array element, it's a local minimum.
  2. If there are two array elements, check each. One must be a local minimum.
  3. Otherwise, look at the middle element of the array. If it's a local minimum, return it. Otherwise, at least one adjacent value must be smaller than this one. Recurse in the half of the array containing that smaller element (but not the middle).

Notice that this has the recurrence relation

T(1) ≤ 1

T(2) ≤ 1

T(n) ≤ T(n / 2) + 1

Using the Master Theorem, you can show that this algorithm runs in time O(log n), as required.

Hope this helps!

Please also notice that this algorithm only works if edges of the array count as local minima if they are smaller than the adjacent element.

Boris Stitnicky
  • 12,444
  • 5
  • 57
  • 74
templatetypedef
  • 362,284
  • 104
  • 897
  • 1,065
  • Yes, but this is O(n) time, not O(log n) – Boris Stitnicky Sep 02 '12 at 17:51
  • 1
    @BorisStitnicky- How so? At each iteration we're doing O(1) work and throwing away half of the array. Can you explain why this works in O(n) time? – templatetypedef Sep 02 '12 at 17:53
  • 1
    You write: "At each step, either the next element is smaller than the previous, or it will be bigger. Eventually, you will either hit the end of the array this way, or you will hit a local minimum". That is, you are looking at the next element. If it is not a local minimum, you have to look next, and again, and again. I fail to see how you are throwing away half of the array at each iteration. – Boris Stitnicky Sep 02 '12 at 17:56
  • 1
    @BorisStitnicky- To elaborate - the first part of my answer is all about building up the intuition for while this algorithm will work, while the actual algorithm is given below. – templatetypedef Sep 02 '12 at 18:00
  • 1
    I have read your hasty agorithm whole. Think about counterexamples, please. It does not matter if sometimes you are able to get the minimum by this method. You have to be able to do it consistently. – Boris Stitnicky Sep 02 '12 at 18:01
  • Is there any similar algorithm if we have to find local minimum in a matrix? – sahil Oct 04 '12 at 09:48
  • @sahil- Yes, but it's much more complicated. I think that's question has alread been asked on SO, so I'd suggest doing a separate search for it. – templatetypedef Oct 04 '12 at 16:53
  • 1
    What if the first half of array in strictly increasing order. And in next half there is increase and decrease in pattern. So based on the proposed algorithm we will never be able to get the local minima. Consider the series 1,2,3,4,5,6,19,25,27,29. Here answer should be 29 but you algorithm will give none – Saurabh Dec 07 '14 at 03:09
  • @saury Can you explain a bit more? In the example that you've given, 29 isn't a local minumum, and the algorithm would report 1 as the answer. Perhaps I'm misinterpreting what you're saying? – templatetypedef Dec 08 '14 at 00:31
  • Sorry I took the series a bit incorrectly. Lets take this example 1,2,3,4,5,6,19,27,25,29. Here 25 should have been local minima but your algorithm will not report it – Saurabh Dec 08 '14 at 01:56
  • 2
    @saury This algorithm won't find 25, but it will find 1, which is also a local minimum. Is that a problem? – templatetypedef Dec 08 '14 at 08:22
  • @templatetypedef so that's the trick? I guess if we strictly ask that the local minimum have neighbors, it won't work then? In the case of 1 being a local minimum, for example, it doesn't have a left neighbor, that's everyone's point. I was also confused about this, but now I see what the trick is. – Pavel Apr 28 '15 at 04:56
  • 2
    Your premise is too strong. It is not necessary for all elements to be distinct, but rather neighbouring elements must be distinct. Your algorithm works totally fine for [3, 2, 3, 2, 3]. – knub Nov 13 '15 at 11:25
7

The number of local minima can be n/2; you can't enumerate them all in O(log n) time.

foxcub
  • 2,517
  • 2
  • 27
  • 27
6

Use a divide-and-conquer algorithm. Let m = n/2, and examine the value A[m] (that is, the element in the middle of the array).

Case 1: A[m−1] < A[m]. Then the left half of the array must contain a local minimum, so recurse on the left half. We can show this by contradiction: assume that A[i] is not a local minimum for each 0 ≤ i < m. Then A[m−1] is not a local minimum, which implies that A[m−2] < A[m−1]. Similarly, A[m −3] < A[m −2]. Continuing in this fashion, we obtain A[0] < A[1]. But then A[0] is a local minimum, contrary to our initial assumption.

Case 2: A[m + 1] > A[m]. Then the right half of the array must contain a local minimum, so recurse on the right half. This is symmetrical to Case 1.

Case 3: A[m − 1] > A[m] and A[m + 1] < A[m]. Then A[m] is a local minimum, so return it. The running time recurrence is T(n) = T(n/2) + Θ(1), which yields T(n) = Θ(log n).

Elad
  • 69
  • 1
  • 1
  • How is your case 2 and 3 are working. For instance in case 3 A[m] > A[m+1], then how A[m] can be local minimum. It has to be smaller than both adjacent points. Isn't? – Cyclotron3x3 Mar 21 '17 at 19:39
  • 2
    This solution is copied verbatim from [this](https://courses.csail.mit.edu/6.006/oldquizzes/solutions/final-s2009-sol.pdf) link. Problem number 5. – Gokul Mar 01 '19 at 05:27
2

You algorithm will not work for this array

15, 13, 12, 18, 19, 20, 7, 6, 5, 4, 3, 2, 1

Here the local minima is 12.. but when I check teh middle element, which is 7, you algorithm will discard the left half (which has the minima) and check in the right half. Hence it does not work

I think it will only work for the special case when the array has a special property that A[1] ≥ A[2] and A[n − 1] ≤ A[n].

Uri Agassi
  • 36,848
  • 14
  • 76
  • 93
user2316569
  • 351
  • 2
  • 8
0

The original question is not complete.

Just found the complete question and full-detailed explanation at Find local minima in an array! - not my blog

Given an array of unique integers whose first two numbers are decreasing and last two numbers are increasing, find a number in the array which is local minima. A number in the array is called local minima if it is smaller than both its left and right numbers.

For example in the array 9,7,2,8,5,6,3,4 2 is a local minima as it is smaller than its left and right number 7 and 8. Similarly 5 is another local minima as it is between 8 and 6, both larger than 5.

You need to find any one of the local minima.

jeffery.yuan
  • 1,177
  • 1
  • 17
  • 27
0

Here is a solution that works on O(log n). Basically, this works on the merge sort approach (divide and conquer).

public class LocalMaxima {
    int []a = {5,8,10,25,6,3,44,51,55,56,57,58,34,5,59};

    @Test 
    public  void localMaxima () {
        System.out.println((a[localMaxima(0,a.length-1)]));
    }

    int localMaxima(int low, int high) {
        if(high-low > 2) {
            int mid = (low+high)/2;
            return maxof(localMaxima(low,mid),localMaxima(mid+1, high));
        }
        else if(high-low == 1) {
            return maxof(high,low);
        }
        else if(high-low == 0) {
            return high;
        }
        if(high-low == 2) {
            return maxof(maxof(low, high),low+1);
        }
        return 0;
    }

    int maxof(int i, int j) {
        if(a[i] <a[j]) {
            return j;
        }
        else {
            return i;
        }
    }
}
Pang
  • 9,564
  • 146
  • 81
  • 122
Sohan
  • 1
0

Actually my previous algorithm can be modified to get all the maxima in O(log n) time. I tested that it works great for all the input provided. Please let me know your feedback

public class LocalMaximas {

@Test
public void test () {
    System.out.println("maximas: please modify code to handle if array size is <= 2");

    int []a = {5,8,10,25,6,3,44,51,55,56,57,58,34,5,59,2};
    localMaximas(a);

    int []b = {9,7,2,8,5,6,3,4, 2}; //9,8,6,4
    localMaximas(b);

    int [] c= {15, 13, 12, 18, 19, 20, 7, 6, 5, 4, 3, 2, 1};//15,20
    localMaximas(c);
}

public  void localMaximas (int [] a) {
    System.out.println("\n\n");
    if(isMaxima(a,0)) {
        System.out.println(a[0]);
    }
    if(isMaxima(a,a.length-1)) {
        System.out.println(a[a.length-1]);
    }
    localMaximas(a,0,a.length-1);
}

int localMaximas(int []a,int low, int high) {
    int mid = (low+high)/2;
    if(high-low > 3) {     // more than 4 items in currently  divided array
        if(isMaxima(a,mid)) {
            System.out.println(a[mid]);
        }   
        localMaximas(a,low, mid);
        localMaximas(a,mid, high);
    }
    else if(high-low == 3){ //exactly 4 items in currently  divided array
        localMaximas(a,low, mid+1);
        localMaximas(a,mid, high);
    }   
    else if((high-low == 2) && (isMaxima(a,low+1))) {
        System.out.println(a[low+1]);
    }
    return 0;
}

int maxof(int []a, int i, int j) {
    if(a[i] <a[j]) {
        return j;
    }
    else {
        return i;
    }
}

boolean isMaxima(int []a ,int mid) {
    if(mid == 0) {
        if(maxof(a, mid, mid+1) == mid) {
            return true;
        }
        else {
            return false;
        }
    }
    else if(mid==a.length-1) {
        if(maxof(a,mid,mid-1) == mid) {
            return true;
        }
        else {
            return false;
        }
    }
    else {
        if((maxof(a, mid, mid+1) == mid) && (maxof(a, mid, mid-1) == mid)) {
            return true;
        }
        else {
            return false;
        }           
    }
}
}
ArK
  • 20,698
  • 67
  • 109
  • 136
Sohan
  • 1