Solution (source code)

= Solution

Use a <lower-bound search> separately in each nondecreasing row:

``
def searchRows(f: Array[Array[Int]], x: Int): Option[(Int, Int)] = {
  val n = f.length
  val m = if (n == 0) 0 else f(0).length
  if (m == 0) return None
  var i = 0
  while (i < n) {
    var lo = 0
    var hi = m
    while (lo < hi) {
      val mid = lo + (hi - lo) / 2
      if (f(i)(mid) < x) lo = mid + 1
      else hi = mid
    }
    if (lo < m && f(i)(lo) == x) return Some((i, lo))
    i += 1
  }
  None
}
``

The outer <loop invariant> says there is no match in earlier rows. The inner invariant is $0\leq lo\leq hi\leq m$, with every value before `lo` smaller than $x$ and every value from `hi` onward at least $x$. <Sortedness> justifies each exclusion, and `hi - lo` strictly decreases. At exit, a match in this row exists exactly when the lower-bound index is inside the row and holds $x$. Failure extends the outer exclusion.

The cost is \b[$O(n(1+\log m))$], conventionally written $O(n\log m)$ for $m\geq2$; the extra one matters when a row has a single entry. Storage is constant. The source's column ranges must use indices strictly below $m$, since `f(i)(m)` is outside the <array>.