The problem
You are given an m x n
integer matrix matrix
with the following two properties:
- Each row is sorted in non-decreasing order.
- The first integer of each row is greater than the last integer of the previous row.
Given an integer target
, return true
if target
is in matrix
or false
otherwise.
You must write a solution in O(log(m * n))
time complexity.
Examples
Input: matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 3
Output: true
Input: matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 13
Output: false
Constraints
- m == matrix.length
- n == matrix[i].length
- 1 <= m, n <= 100
- -10^4 <= matrix[i][j], target <= 10^4
Explanation
From the description of the problem, we learn that each row is sorted and the next row is going to be greater than the previous one. Therefore, each number throughout the matrix is going to be in sorted order. The brute-force way to solve this problem is to search every single value in our input. This algorithm will take O(m * n) time.
Let’s look at another solution assuming that we just had a single row, and it was in sorted order.
We know that the most optimal algorithm that can search for a target would be binary search because we can do search in O(logn)
time.
We can do it by running binary search on every single row until we find our target
. This way we can satisfy the first property from our description. The time complexity for this solution will be O(m * logn)
, but we can do better and optimize it even further.
We can optimize it further by following the second property. By knowing the fact that the next first row will be greater than the last integer in the previous row, we can apply binary search to determine in which row we can find our potential target
.
If values in our current row are greater than our target
, we will be crossing out the current row and will be looking at the row above the current row, because the top row will have smaller values than the bottom row.
This approach is more efficient because the first binary search will search for the row and will take O(logm)
time, and the second binary search will search for the target
value inside the row and will take O(logn)
time. So the overall time complexity is O(logm + logn)
.
Binary Search Solution
func searchMatrix(_ matrix: [[Int]], _ target: Int) -> Bool {
let rows = matrix.count
let cols = matrix[0].count
var topRow = 0
var bottomRow = rows - 1
while topRow <= bottomRow {
let m = (bottomRow + topRow) / 2
if matrix[m].last! < target {
topRow = m + 1
} else if matrix[m][0] > target {
bottomRow = m - 1
} else {
break
}
}
let row = (bottomRow + topRow) / 2
var l = 0
var r = cols - 1
while l <= r {
let m = (l + r) / 2
if matrix[row][m] < target {
l = m + 1
} else if matrix[row][m] > target {
r = m - 1
} else {
return true
}
}
return false
}
Time/ Space complexity
- Time complexity: O(logm + logn), which reduces to O(logm * n)
- Space complexity: O(1)
- where
m
is the number of rows andn
is the number of columns of the matrix
Binary Search (One Pass) Solution
func searchMatrix(_ matrix: [[Int]], _ target: Int) -> Bool {
let rows = matrix.count
let cols = matrix[0].count
var l = 0
var r = rows * cols - 1
while l <= r {
let m = (l + r) / 2
let row = m / cols
let col = m % cols
if matrix[row][col] < target {
l = m + 1
} else if matrix[row][col] > target {
r = m - 1
} else {
return true
}
}
return false
}
Time/ Space complexity
- Time complexity: O(logm * n)
- Space complexity: O(1)
- where
m
is the number of rows andn
is the number of columns of the matrix