Solution (source code)

= Solution

Maintain a remaining rectangle with its top-right corner at `(i,j)`:

``
def searchMonotone(f: Array[Array[Int]], x: Int): Option[(Int, Int)] = {
  val n = f.length
  val m = if (n == 0) 0 else f(0).length
  var i = 0
  var j = m - 1
  while (i < n && j >= 0) {
    val v = f(i)(j)
    if (v == x) return Some((i, j))
    else if (v < x) i += 1
    else j -= 1
  }
  None
}
``

The <loop invariant> is that any match must lie in rows $i,\ldots,n-1$ and columns $0,\ldots,j$. Smaller corner values exclude the current row, larger ones exclude the current column, as shown in part (d). The <loop variant> $(n-i)+(j+1)$ is nonnegative while the rectangle is nonempty and decreases by one on every unsuccessful inspection. When the loop exits, the rectangle is empty, so absence is established.

\b[At most $n+m-1$ entries are inspected when both dimensions are positive], giving $O(n+m)$ <running time> and $O(1)$ <auxiliary space>. An empty dimension causes no entry access. Duplicates do not affect the argument, since every exclusion uses a <strict inequality> relative to the target.