Skip to content

Sorted Matrix Search

Search a matrix sorted by rows and columns using divide and conquer.

Sorted matrix search finds a target in a matrix where each row and each column is sorted. Unlike saddleback search, this approach uses divide and conquer to recursively eliminate regions.

Problem

Given a matrix AA of size n×mn \times m such that:

A[i][j]A[i][j+1] A[i][j] \le A[i][j+1] A[i][j]A[i+1][j] A[i][j] \le A[i+1][j]

find a position (i,j)(i, j) such that:

A[i][j]=x A[i][j] = x

If no such position exists, return (1,1)(-1, -1).

Key Idea

Select a middle column and perform binary search on that column to locate a candidate row.

This splits the matrix into regions where the target may still exist. Discard regions that cannot contain the target.

The process repeats recursively on smaller submatrices.

Algorithm

sorted_matrix_search(A, x, top, bottom, left, right):
    if top > bottom or left > right:
        return (-1, -1)

    mid_col = (left + right) // 2

    # binary search in mid column
    l = top
    r = bottom
    while l <= r:
        mid_row = (l + r) // 2
        if A[mid_row][mid_col] == x:
            return (mid_row, mid_col)
        elif A[mid_row][mid_col] < x:
            l = mid_row + 1
        else:
            r = mid_row - 1

    # search in two submatrices
    # top-right
    res = sorted_matrix_search(A, x, top, r, mid_col + 1, right)
    if res != (-1, -1):
        return res

    # bottom-left
    return sorted_matrix_search(A, x, l, bottom, left, mid_col - 1)

Wrapper:

search(A, x):
    return sorted_matrix_search(A, x, 0, n-1, 0, m-1)

Example

Let

A=[14711 25812 36916 10131417] A = \begin{bmatrix} 1 & 4 & 7 & 11 \ 2 & 5 & 8 & 12 \ 3 & 6 & 9 & 16 \ 10 & 13 & 14 & 17 \end{bmatrix}

and

x=6 x = 6

The algorithm narrows the search to the appropriate submatrix and eventually finds:

(2,1) (2, 1)

Correctness

Binary search in the middle column identifies a boundary row:

  • All elements above are A[mid]\le A[mid]
  • All elements below are A[mid]\ge A[mid]

If the target is not found, only two subregions can still contain it:

  • upper-right region
  • lower-left region

Other regions are eliminated because their values are strictly too small or too large.

Recursive calls preserve correctness by only exploring feasible regions.

Complexity

Let nn be rows and mm be columns.

costvalue
timeO(nlogm)O(n \log m) or O(mlogn)O(m \log n) depending on orientation
spaceO(logn)O(\log n) recursion

This is typically slower than saddleback search for a single query.

Comparison

methodrequirementtime
saddleback searchrow and column sortedO(n+m)O(n + m)
divide and conquerrow and column sortedO(nlogm)O(n \log m)

Saddleback search is simpler and faster in most cases.

When to Use

  • When recursive decomposition fits the problem structure
  • When extending to higher dimensions
  • When combining with other divide-and-conquer strategies

Notes

  • More complex than necessary for simple lookup
  • Demonstrates matrix partitioning technique
  • Can be adapted for parallel execution

Implementation

def sorted_matrix_search(a, x):
    if not a or not a[0]:
        return -1, -1

    n, m = len(a), len(a[0])

    def helper(top, bottom, left, right):
        if top > bottom or left > right:
            return -1, -1

        mid_col = (left + right) // 2

        l, r = top, bottom
        while l <= r:
            mid_row = (l + r) // 2
            if a[mid_row][mid_col] == x:
                return mid_row, mid_col
            elif a[mid_row][mid_col] < x:
                l = mid_row + 1
            else:
                r = mid_row - 1

        res = helper(top, r, mid_col + 1, right)
        if res != (-1, -1):
            return res

        return helper(l, bottom, left, mid_col - 1)

    return helper(0, n - 1, 0, m - 1)
func SortedMatrixSearch(a [][]int, x int) (int, int) {
    if len(a) == 0 || len(a[0]) == 0 {
        return -1, -1
    }

    var helper func(int, int, int, int) (int, int)

    helper = func(top, bottom, left, right int) (int, int) {
        if top > bottom || left > right {
            return -1, -1
        }

        midCol := (left + right) / 2

        l, r := top, bottom
        for l <= r {
            midRow := (l + r) / 2
            if a[midRow][midCol] == x {
                return midRow, midCol
            } else if a[midRow][midCol] < x {
                l = midRow + 1
            } else {
                r = midRow - 1
            }
        }

        if i, j := helper(top, r, midCol+1, right); i != -1 {
            return i, j
        }

        return helper(l, bottom, left, midCol-1)
    }

    return helper(0, len(a)-1, 0, len(a[0])-1)
}