Idea 1 (binary search)

class Solution {
  public int kthSmallest(int[][] matrix, int k) {
    int n = matrix.length;

    // the ans is within the left and right range (inclusive)
    int left = matrix[0][0];
    int right = matrix[n - 1][n - 1];

    // Since it is within the range, if there is only one value left in the
    // range, then it must be the answer!
    while (left < right) {
      int mid = left + (right - left) / 2;

      int count = countSmallerEqual(mid, matrix);

      if (count < k) { // value is bigger than mid
        left = mid + 1;
      } else { // value is mid or smaller
        right = mid;
      }
    }

    return left;
  }

  // Time O(n), instead of O(n^2) which goes through each cell
  public int countSmallerEqual(int midVal, int[][] matrix) {
    int n = matrix.length;

    int r = n - 1; // we check from the bottom
    int c = 0; // we start from first column

    int count = 0;

    while (r >= 0 && c < n) { // when r<0, it is out of the matrix
      if (matrix[r][c]
          <= midVal) { // midVal covers the entire column from the r and below
        count += (r + 1);
        c++; // go check the next column
      } else {
        r--; // the value in the same row is bigger, so 100% can be ignore
      }
    }

    return count;
  }
}

Idea 2 (priority queue)