How do I search for a number in a 2d array sorted left to right and top to bottom?

后端 未结 20 738
暖寄归人
暖寄归人 2020-11-22 13:03

I was recently given this interview question and I\'m curious what a good solution to it would be.

Say I\'m given a 2d array where all the numbers i

相关标签:
20条回答
  • 2020-11-22 13:26

    Binary search through the diagonal of the array is the best option. We can find out whether the element is less than or equal to the elements in the diagonal.

    0 讨论(0)
  • 2020-11-22 13:26

    EDIT:

    I misunderstood the question. As the comments point out this only works in the more restricted case.

    In a language like C that stores data in row-major order, simply treat it as a 1D array of size n * m and use a binary search.

    0 讨论(0)
  • 2020-11-22 13:28

    The two main answers give so far seem to be the arguably O(log N) "ZigZag method" and the O(N+M) Binary Search method. I thought I'd do some testing comparing the two methods with some various setups. Here are the details:

    The array is N x N square in every test, with N varying from 125 to 8000 (the largest my JVM heap could handle). For each array size, I picked a random place in the array to put a single 2. I then put a 3 everywhere possible (to the right and below of the 2) and then filled the rest of the array with 1. Some of the earlier commenters seemed to think this type of setup would yield worst case run time for both algorithms. For each array size, I picked 100 different random locations for the 2 (search target) and ran the test. I recorded avg run time and worst case run time for each algorithm. Because it was happening too fast to get good ms readings in Java, and because I don't trust Java's nanoTime(), I repeated each test 1000 times just to add a uniform bias factor to all the times. Here are the results:

    enter image description here

    ZigZag beat binary in every test for both avg and worst case times, however, they are all within an order of magnitude of each other more or less.

    Here is the Java code:

    public class SearchSortedArray2D {
    
        static boolean findZigZag(int[][] a, int t) {
            int i = 0;
            int j = a.length - 1;
            while (i <= a.length - 1 && j >= 0) {
                if (a[i][j] == t) return true;
                else if (a[i][j] < t) i++;
                else j--;
            }
            return false;
        }
    
        static boolean findBinarySearch(int[][] a, int t) {
            return findBinarySearch(a, t, 0, 0, a.length - 1, a.length - 1);
        }
    
        static boolean findBinarySearch(int[][] a, int t,
                int r1, int c1, int r2, int c2) {
            if (r1 > r2 || c1 > c2) return false; 
            if (r1 == r2 && c1 == c2 && a[r1][c1] != t) return false;
            if (a[r1][c1] > t) return false;
            if (a[r2][c2] < t) return false;
    
            int rm = (r1 + r2) / 2;
            int cm = (c1 + c2) / 2;
            if (a[rm][cm] == t) return true;
            else if (a[rm][cm] > t) {
                boolean b1 = findBinarySearch(a, t, r1, c1, r2, cm - 1);
                boolean b2 = findBinarySearch(a, t, r1, cm, rm - 1, c2);
                return (b1 || b2);
            } else {
                boolean b1 = findBinarySearch(a, t, r1, cm + 1, rm, c2);
                boolean b2 = findBinarySearch(a, t, rm + 1, c1, r2, c2);
                return (b1 || b2);
            }
        }
    
        static void randomizeArray(int[][] a, int N) {
            int ri = (int) (Math.random() * N);
            int rj = (int) (Math.random() * N);
            a[ri][rj] = 2;
            for (int i = 0; i < N; i++) {
                for (int j = 0; j < N; j++) {
                    if (i == ri && j == rj) continue;
                    else if (i > ri || j > rj) a[i][j] = 3;
                    else a[i][j] = 1;
                }
            }
        }
    
        public static void main(String[] args) {
    
            int N = 8000;
            int[][] a = new int[N][N];
            int randoms = 100;
            int repeats = 1000;
    
            long start, end, duration;
            long zigMin = Integer.MAX_VALUE, zigMax = Integer.MIN_VALUE;
            long binMin = Integer.MAX_VALUE, binMax = Integer.MIN_VALUE;
            long zigSum = 0, zigAvg;
            long binSum = 0, binAvg;
    
            for (int k = 0; k < randoms; k++) {
                randomizeArray(a, N);
    
                start = System.currentTimeMillis();
                for (int i = 0; i < repeats; i++) findZigZag(a, 2);
                end = System.currentTimeMillis();
                duration = end - start;
                zigSum += duration;
                zigMin = Math.min(zigMin, duration);
                zigMax = Math.max(zigMax, duration);
    
                start = System.currentTimeMillis();
                for (int i = 0; i < repeats; i++) findBinarySearch(a, 2);
                end = System.currentTimeMillis();
                duration = end - start;
                binSum += duration;
                binMin = Math.min(binMin, duration);
                binMax = Math.max(binMax, duration);
            }
            zigAvg = zigSum / randoms;
            binAvg = binSum / randoms;
    
            System.out.println(findZigZag(a, 2) ?
                    "Found via zigzag method. " : "ERROR. ");
            //System.out.println("min search time: " + zigMin + "ms");
            System.out.println("max search time: " + zigMax + "ms");
            System.out.println("avg search time: " + zigAvg + "ms");
    
            System.out.println();
    
            System.out.println(findBinarySearch(a, 2) ?
                    "Found via binary search method. " : "ERROR. ");
            //System.out.println("min search time: " + binMin + "ms");
            System.out.println("max search time: " + binMax + "ms");
            System.out.println("avg search time: " + binAvg + "ms");
        }
    }
    
    0 讨论(0)
  • 2020-11-22 13:29

    Here's a simple approach:

    1. Start at the bottom-left corner.
    2. If the target is less than that value, it must be above us, so move up one.
    3. Otherwise we know that the target can't be in that column, so move right one.
    4. Goto 2.

    For an NxM array, this runs in O(N+M). I think it would be difficult to do better. :)


    Edit: Lots of good discussion. I was talking about the general case above; clearly, if N or M are small, you could use a binary search approach to do this in something approaching logarithmic time.

    Here are some details, for those who are curious:

    History

    This simple algorithm is called a Saddleback Search. It's been around for a while, and it is optimal when N == M. Some references:

    • David Gries, The Science of Programming. Springer-Verlag, 1989.
    • Edsgar Dijkstra, The Saddleback Search. Note EWD-934, 1985.

    However, when N < M, intuition suggests that binary search should be able to do better than O(N+M): For example, when N == 1, a pure binary search will run in logarithmic rather than linear time.

    Worst-case bound

    Richard Bird examined this intuition that binary search could improve the Saddleback algorithm in a 2006 paper:

    • Richard S. Bird, Improving Saddleback Search: A Lesson in Algorithm Design, in Mathematics of Program Construction, pp. 82--89, volume 4014, 2006.

    Using a rather unusual conversational technique, Bird shows us that for N <= M, this problem has a lower bound of Ω(N * log(M/N)). This bound make sense, as it gives us linear performance when N == M and logarithmic performance when N == 1.

    Algorithms for rectangular arrays

    One approach that uses a row-by-row binary search looks like this:

    1. Start with a rectangular array where N < M. Let's say N is rows and M is columns.
    2. Do a binary search on the middle row for value. If we find it, we're done.
    3. Otherwise we've found an adjacent pair of numbers s and g, where s < value < g.
    4. The rectangle of numbers above and to the left of s is less than value, so we can eliminate it.
    5. The rectangle below and to the right of g is greater than value, so we can eliminate it.
    6. Go to step (2) for each of the two remaining rectangles.

    In terms of worst-case complexity, this algorithm does log(M) work to eliminate half the possible solutions, and then recursively calls itself twice on two smaller problems. We do have to repeat a smaller version of that log(M) work for every row, but if the number of rows is small compared to the number of columns, then being able to eliminate all of those columns in logarithmic time starts to become worthwhile.

    This gives the algorithm a complexity of T(N,M) = log(M) + 2 * T(M/2, N/2), which Bird shows to be O(N * log(M/N)).

    Another approach posted by Craig Gidney describes an algorithm similar the approach above: it examines a row at a time using a step size of M/N. His analysis shows that this results in O(N * log(M/N)) performance as well.

    Performance Comparison

    Big-O analysis is all well and good, but how well do these approaches work in practice? The chart below examines four algorithms for increasingly "square" arrays:

    algorithm performance vs squareness

    (The "naive" algorithm simply searches every element of the array. The "recursive" algorithm is described above. The "hybrid" algorithm is an implementation of Gidney's algorithm. For each array size, performance was measured by timing each algorithm over fixed set of 1,000,000 randomly-generated arrays.)

    Some notable points:

    • As expected, the "binary search" algorithms offer the best performance on rectangular arrays and the Saddleback algorithm works the best on square arrays.
    • The Saddleback algorithm performs worse than the "naive" algorithm for 1-d arrays, presumably because it does multiple comparisons on each item.
    • The performance hit that the "binary search" algorithms take on square arrays is presumably due to the overhead of running repeated binary searches.

    Summary

    Clever use of binary search can provide O(N * log(M/N) performance for both rectangular and square arrays. The O(N + M) "saddleback" algorithm is much simpler, but suffers from performance degradation as arrays become increasingly rectangular.

    0 讨论(0)
  • 2020-11-22 13:29

    I have a recursive Divide & Conquer Solution. Basic Idea for one step is: We know that the Left-Upper(LU) is smallest and the right-bottom(RB) is the largest no., so the given No(N) must: N>=LU and N<=RB

    IF N==LU and N==RB::::Element Found and Abort returning the position/Index If N>=LU and N<=RB = FALSE, No is not there and abort. If N>=LU and N<=RB = TRUE, Divide the 2D array in 4 equal parts of 2D array each in logical manner.. And then apply the same algo step to all four sub-array.

    My Algo is Correct I have implemented on my friends PC. Complexity: each 4 comparisons can b used to deduce the total no of elements to one-fourth at its worst case.. So My complexity comes to be 1 + 4 x lg(n) + 4 But really expected this to be working on O(n)

    I think something is wrong somewhere in my calculation of Complexity, please correct if so..

    0 讨论(0)
  • 2020-11-22 13:29

    If O(M log(N)) solution is ok for an MxN array -

    template <size_t n>
    struct MN * get(int a[][n], int k, int M, int N){
      struct MN *result = new MN;
      result->m = -1;
      result->n = -1;
    
      /* Do a binary search on each row since rows (and columns too) are sorted. */
      for(int i = 0; i < M; i++){
        int lo = 0; int hi = N - 1;
        while(lo <= hi){
          int mid = lo + (hi-lo)/2;
          if(k < a[i][mid]) hi = mid - 1;
          else if (k > a[i][mid]) lo = mid + 1;
          else{
            result->m = i;
            result->n = mid;
            return result;
          }
        }
      }
      return result;
    }
    

    Working C++ demo.

    Please do let me know if this wouldn't work or if there is a bug it it.

    0 讨论(0)
提交回复
热议问题