Find a number in an array

gabi :

Is there a solution for the following question that has O(n) efficiency?

You need to find a cell in an array such that all of the numbers before it are lower than it, and all the numbers after it are higher than it. You should ignore first and last cells.

For example, consider the following list:

1, 3, 2, 6, 5, 7, 9, 8, 10, 8, 11

In that case, the answer would be the number 7 at index 5.

paxdiablo :

Yes, it certainly can be done in O(n) time. A couple of methods follow.

The first one is more useful to find all candidate cells. Make a single O(n) pass of the data setting two extra items per cell, hence O(n) space (a non-trivial number of optimisation problems can be solved by trading space for time).

The two items you need to calculate for each cell are the highest value to the left and the smallest value to the right. The first pass sets these items for all cells bar the one at the end where it doesn't make sense (pseudo-code obviously):

# Set current values.

highLeftVal = cell[0]
lowRightVal = cell[cell.lastIndex]

# For running right and left through array.

rightIdx = cell.lastIndex
for each leftIdx 1 thru cell.lastIndex inclusive:
    # Left index done by loop, right one manually.

    rightIdx = rightIdx - 1

    # Store current values and update.

    highLeft[leftIdx] = highLeftVal
    if cell[leftIdx] > highLeftVal: highLeftVal = cell[leftIdx]

    lowRight[rightIdx] = lowRightVal
    if cell[rightIdx] < lowRightVal: lowRightVal = cell[rightIdx]

Then it's a simple matter of checking every cell (bar the first and last) to ensure that the value is both greater than (this answer assumes, as per your question, that "higher/lower" is literal, not "greater/less than or equal to") the highest on the left and less than the lowest on the right:

for each idx 1 thru cell.lastIndex-1 inclusive:
    if cell[idx] > highLeft[idx] and cell[idx] < lowRight[idx]
        print "Found value ", cell[idx], " at index ", idx

You can see the results of the initial pass below:

highLeft:   -  1  3  3  6  6  7  9  9 10 10
cells   :   1  3  2  6  5  7  9  8 10  8 11
lowRight:   2  2  5  5  7  8  8  8  8 11  -
                           ^

The only candidate cell where the value is ordered (non-inclusive) with respect to the two values above and below it, is the 7 marked with ^.


Now keep in mind that's a relatively easy-to-understand solution that can find multiple items that satisfy the constraints. Given that you only need one item, it's possible to get better performance (though it's still O(n)).

The basic idea is to traverse the array from left to right and, for each cell, you check whether everything on the left is lower and everything on the right is higher.

The first bit of that is easy since, by traversing left to right, you can remember the highest value encountered. The second bit seems to involve looking into the future somehow but there's a trick you can use to avoid this "temporal gymnastics".

The idea is to maintain both the highest value seen on the left of the current cell and the index of the current answer (which is initially set to a sentinel value).

If the current answer is the sentinel value, the first cell that satisfies the "is greater than everything on the left" is chosen as the possible answer.

And, as long as that remains the case, that's the cell you opt for. However, as soon as you find one less than or equal to it on its right, it's no longer valid so you discard it and start searching again.

This searching happens from the current point, not back at the start, because:

  • everything after the current answer up to but excluding this (lesser or equal) cell is higher than the current answerr, otherwise you would have already found a lesser or equal cell; and
  • this cell must therefore be less than or equal to every cell in that range since it's less than or equal to the current answer; therefore
  • no cell in that range is valid, they're all greater than or equal to this one.

Once you've finished processing the non-end items, your answer will either be the sentinel or a cell that almost satisfies the constraints.

I say "almost" because there's one final check required to ensure that the final item is greater than it, since you performed no checks on that item as part of the traversal.

Hence the pseudo-code for that beast is something like:

# Store max on left and start with sentinel.

maxToLeft = cell[0]
answer = -1

for checking = 1 to cell.lastIndex-1 inclusive:
    switch on answer:
        # Save if currently sentinel and item valid.
        case -1:
            if cell[checking] > maxToLeft:
                answer = checking

        # Set back to sentinel if saved answer is now invalid.
        otherwise:
            if cell[answer] >= cell[checking]:
                answer = -1

    # Ensure we have updated max on left.

    if cell[checking] > maxToLeft:
        maxToLeft = cell[checking]

# Final check against last cell.

if answer != -1:
    if cell[cell.lastIndex] <= cell[answer]:
        answer = -1

Since my pseudo-code is (heavily) based on Python, it's a fairly simple matter to provide a more concrete example of the code in action. First, the "find every possibility" option:

cell = [1, 3, 2, 6, 5, 7, 9, 8, 10, 8, 11]

highLeft = [0] * len(cell)
lowRight = [0] * len(cell)

highLeftVal = cell[0]
lowRightVal = cell[len(cell)-1]

rightIdx = len(cell) - 1
for leftIdx in range(1, len(cell)):
    rightIdx = rightIdx - 1

    highLeft[leftIdx] = highLeftVal
    if cell[leftIdx] > highLeftVal: highLeftVal = cell[leftIdx]

    lowRight[rightIdx] = lowRightVal
    if cell[rightIdx] < lowRightVal: lowRightVal = cell[rightIdx]

print(highLeft)
print(cell)
print(lowRight)

for idx in range(1, len(cell) - 1):
    if cell[idx] > highLeft[idx] and cell[idx] < lowRight[idx]:
        print("Found value", cell[idx], "at index", idx)

And the second, slightly more efficient option, but only able to find one possibility:

cell = [1, 3, 2, 6, 5, 7, 9, 8, 10, 8, 11]
maxToLeft = cell[0]
answer = -1
for checking in range(1, len(cell) - 1):
    if answer == -1:
        if cell[checking] > maxToLeft:
            answer = checking
    else:
        if cell[answer] >=cell[checking]:
            answer = -1
    if cell[checking] > maxToLeft:
        maxToLeft = cell[checking]

if answer != -1:
    if cell[len(cell] - 1] <= cell[answer]:
        answer = -1

if answer == -1:
    print ("Not found")
else:
    print("Found value", cell[answer], "at index", answer);


print(highLeft)
print(cell)
print(lowRight)

for idx in range(1, len(cell) - 1):
    if cell[idx] > highLeft[idx] and cell[idx] < lowRight[idx]:
        print("Found value", cell[idx], "at index", idx)

The output of both of those (though the latter example only shows the final line) shows basically what the pseudo-code was meant to illustrate:

[0, 1, 3, 3, 6, 6, 7, 9, 9, 10, 10]
[1, 3, 2, 6, 5, 7, 9, 8, 10, 8, 11]
[2, 2, 5, 5, 7, 8, 8, 8, 8, 11, 0]
Found value 7 at index 5

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=451050&siteId=1