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
This problem takes Θ(b lg(t))
time, where b = min(w,h)
and t=b/max(w,h)
. I discuss the solution in this blog post.
Lower bound
An adversary can force an algorithm to make Ω(b lg(t))
queries, by restricting itself to the main diagonal:
Legend: white cells are smaller items, gray cells are larger items, yellow cells are smaller-or-equal items and orange cells are larger-or-equal items. The adversary forces the solution to be whichever yellow or orange cell the algorithm queries last.
Notice that there are b
independent sorted lists of size t
, requiring Ω(b lg(t))
queries to completely eliminate.
Algorithm
w >= h
)t
to the left of the top right corner of the valid area
t
cells in the row with a binary search. If a matching item is found while doing this, return with its position.t
short columns.Finding an item:
Determining an item doesn't exist:
Legend: white cells are smaller items, gray cells are larger items, and the green cell is an equal item.
Analysis
There are b*t
short columns to eliminate. There are b
long rows to eliminate. Eliminating a long row costs O(lg(t))
time. Eliminating t
short columns costs O(1)
time.
In the worst case we'll have to eliminate every column and every row, taking time O(lg(t)*b + b*t*1/t) = O(b lg(t))
.
Note that I'm assuming lg
clamps to a result above 1 (i.e. lg(x) = log_2(max(2,x))
). That's why when w=h
, meaning t=1
, we get the expected bound of O(b lg(1)) = O(b) = O(w+h)
.
Code
public static Tuple TryFindItemInSortedMatrix(this IReadOnlyList> grid, T item, IComparer comparer = null) {
if (grid == null) throw new ArgumentNullException("grid");
comparer = comparer ?? Comparer.Default;
// check size
var width = grid.Count;
if (width == 0) return null;
var height = grid[0].Count;
if (height < width) {
var result = grid.LazyTranspose().TryFindItemInSortedMatrix(item, comparer);
if (result == null) return null;
return Tuple.Create(result.Item2, result.Item1);
}
// search
var minCol = 0;
var maxRow = height - 1;
var t = height / width;
while (minCol < width && maxRow >= 0) {
// query the item in the minimum column, t above the maximum row
var luckyRow = Math.Max(maxRow - t, 0);
var cmpItemVsLucky = comparer.Compare(item, grid[minCol][luckyRow]);
if (cmpItemVsLucky == 0) return Tuple.Create(minCol, luckyRow);
// did we eliminate t rows from the bottom?
if (cmpItemVsLucky < 0) {
maxRow = luckyRow - 1;
continue;
}
// we eliminated most of the current minimum column
// spend lg(t) time eliminating rest of column
var minRowInCol = luckyRow + 1;
var maxRowInCol = maxRow;
while (minRowInCol <= maxRowInCol) {
var mid = minRowInCol + (maxRowInCol - minRowInCol + 1) / 2;
var cmpItemVsMid = comparer.Compare(item, grid[minCol][mid]);
if (cmpItemVsMid == 0) return Tuple.Create(minCol, mid);
if (cmpItemVsMid > 0) {
minRowInCol = mid + 1;
} else {
maxRowInCol = mid - 1;
maxRow = mid - 1;
}
}
minCol += 1;
}
return null;
}