Sorted Matrix Search
Search a matrix sorted by rows and columns using divide and conquer.
Sorted Matrix Search
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 $A$ of size $n \times m$ such that:
$$ A[i][j] \le A[i][j+1] $$
$$ A[i][j] \le A[i+1][j] $$
find a position $(i, j)$ such that:
$$ A[i][j] = x $$
If no such position exists, return $(-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 =
\begin{bmatrix}
1 & 4 & 7 & 11
2 & 5 & 8 & 12
3 & 6 & 9 & 16
10 & 13 & 14 & 17
\end{bmatrix}
$$
and
$$ x = 6 $$
The algorithm narrows the search to the appropriate submatrix and eventually finds:
$$ (2, 1) $$
Correctness
Binary search in the middle column identifies a boundary row:
- All elements above are $\le A[mid]$
- All elements below are $\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 $n$ be rows and $m$ be columns.
| cost | value |
|---|---|
| time | $O(n \log m)$ or $O(m \log n)$ depending on orientation |
| space | $O(\log n)$ recursion |
This is typically slower than saddleback search for a single query.
Comparison
| method | requirement | time |
|---|---|---|
| saddleback search | row and column sorted | $O(n + m)$ |
| divide and conquer | row and column sorted | $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)
}