1901. Find a Peak Element II
Problem Description
You are given a 2D grid (matrix) where you need to find a peak element. A peak element is defined as an element that is strictly greater than all of its adjacent neighbors (left, right, top, and bottom).
The matrix mat
has the following properties:
- It is 0-indexed with dimensions
m x n
- No two adjacent cells have equal values (all adjacent elements are different)
- The entire matrix is conceptually surrounded by an outer perimeter with value
-1
in each cell
Your task is to find any peak element mat[i][j]
and return its position as a length-2 array [i,j]
.
Time Complexity Requirement: Your algorithm must run in O(m log(n))
or O(n log(m))
time, where m
is the number of rows and n
is the number of columns.
For example, if an element at position [i,j]
has value 10
, and its adjacent neighbors have values 5
(left), 7
(right), 3
(top), and 8
(bottom), then this element is a peak because 10 > 5
, 10 > 7
, 10 > 3
, and 10 > 8
.
The logarithmic time complexity requirement suggests that a binary search approach should be used rather than checking every element in the matrix.
Intuition
The time complexity requirement of O(m log(n))
or O(n log(m))
immediately suggests we need to use binary search rather than checking all elements. But how can we apply binary search to a 2D grid?
Let's think about what happens when we pick a row and find its maximum element. Suppose we're at row i
and the maximum element in this row is at column j
. Now we have mat[i][j]
.
The key insight is to compare mat[i][j]
with the element directly below it: mat[i+1][j]
. There are two cases:
-
If
mat[i][j] > mat[i+1][j]
: Sincemat[i][j]
is already the maximum in its row, it's greater than its left and right neighbors. If it's also greater than the element below, we need to check if it's greater than the element above. If not, then the element above must be even larger. This creates an "upward flow" - we keep moving up to find larger elements. Eventually, we'll reach row 0, where the element has no row above it (conceptually it's bordered by-1
), making it a peak. So there must be a peak in rows[0...i]
. -
If
mat[i][j] < mat[i+1][j]
: By similar logic, if the element below is larger, we have a "downward flow". We keep moving down to find larger elements. Eventually, we'll reach the last row where there's no row below (bordered by-1
), guaranteeing a peak exists in rows[i+1...m-1]
.
This observation allows us to eliminate half of the search space with each comparison! We can perform binary search on rows:
- Start with the middle row
- Find the maximum element in that row
- Compare it with the element directly below
- Based on the comparison, we know which half contains a peak
- Continue binary search in that half
The beauty of this approach is that we're guaranteed to find a peak because:
- The matrix is surrounded by
-1
values - No two adjacent cells are equal
- Following the direction of increasing values must eventually lead to a peak (it can't increase forever due to the boundary)
Learn more about Binary Search patterns.
Solution Approach
We implement binary search on the rows of the matrix. Here's the step-by-step approach:
Initialize Binary Search Boundaries:
- Set
l = 0
(first row) andr = len(mat) - 1
(last row) - These represent our search space for rows
Binary Search Process:
While l < r
:
- Calculate the middle row:
mid = (l + r) >> 1
(using right shift for division by 2) - Find the maximum element in row
mid
and its column indexj
:j = mat[mid].index(max(mat[mid]))
- This finds the column position of the maximum value in the current row
- Compare
mat[mid][j]
with the element directly below itmat[mid + 1][j]
:- If
mat[mid][j] > mat[mid + 1][j]
: The peak must be in rows[0...mid]
- Update
r = mid
(search in upper half including current row)
- Update
- Else (
mat[mid][j] < mat[mid + 1][j]
): The peak must be in rows[mid + 1...m - 1]
- Update
l = mid + 1
(search in lower half excluding current row)
- Update
- If
Return the Peak:
- When the loop exits,
l == r
, meaning we've narrowed down to a single row - Find the maximum element in this final row:
mat[l].index(max(mat[l]))
- Return
[l, mat[l].index(max(mat[l]))]
as the peak position
Why This Works:
- In each iteration, we eliminate half of the remaining rows
- We're guaranteed to find a peak because:
- If we move up (when
mat[mid][j] > mat[mid + 1][j]
), we're moving towards rows where eventually the top boundary (-1
) ensures a peak exists - If we move down (when
mat[mid][j] < mat[mid + 1][j]
), we're moving towards rows where eventually the bottom boundary (-1
) ensures a peak exists
- If we move up (when
- The maximum element of the final row
l
is guaranteed to be a peak because our binary search has ensured it's greater than the element in the adjacent row we came from
Time Complexity: O(m log m + m × n)
where finding max in each row takes O(n)
and we do this O(log m)
times during binary search, plus O(m × n)
for the initial max operations. However, if we optimize by caching or using a different approach for finding row maximum, we can achieve O(n log m)
.
Space Complexity: O(1)
as we only use a constant amount of extra space for variables.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through finding a peak in this 3×4 matrix:
mat = [[1, 4, 3, 2], [9, 5, 6, 7], [8, 10, 11, 12]]
Initial Setup:
l = 0
(first row),r = 2
(last row)- We need to find a peak element
Iteration 1:
- Calculate middle row:
mid = (0 + 2) >> 1 = 1
- Find maximum in row 1:
[9, 5, 6, 7]
- Maximum is
9
at column index0
- So we have
mat[1][0] = 9
- Maximum is
- Compare with element below:
mat[1][0]
vsmat[2][0]
9 > 8
, so the peak must be in rows[0...1]
- Update
r = 1
Iteration 2:
- Now
l = 0
,r = 1
- Calculate middle row:
mid = (0 + 1) >> 1 = 0
- Find maximum in row 0:
[1, 4, 3, 2]
- Maximum is
4
at column index1
- So we have
mat[0][1] = 4
- Maximum is
- Compare with element below:
mat[0][1]
vsmat[1][1]
4 < 5
, so the peak must be in rows[1...1]
- Update
l = 1
Final Step:
- Now
l = r = 1
, loop exits - Find maximum in row 1:
[9, 5, 6, 7]
- Maximum is
9
at column index0
- Maximum is
- Return
[1, 0]
Verification: Is mat[1][0] = 9
a peak?
- Left neighbor: conceptually
-1
(boundary) ✓9 > -1
- Right neighbor:
mat[1][1] = 5
✓9 > 5
- Top neighbor:
mat[0][0] = 1
✓9 > 1
- Bottom neighbor:
mat[2][0] = 8
✓9 > 8
Yes! The element 9
at position [1, 0]
is indeed a peak.
Note: The algorithm could also find other valid peaks like 12
at [2, 3]
. The problem only requires finding any one peak, not all peaks or the global maximum.
Solution Implementation
1class Solution:
2 def findPeakGrid(self, mat: List[List[int]]) -> List[int]:
3 # Binary search on rows to find a peak element
4 left, right = 0, len(mat) - 1
5
6 while left < right:
7 # Calculate middle row index
8 mid_row = (left + right) >> 1 # Equivalent to (left + right) // 2
9
10 # Find the column index of the maximum element in the middle row
11 max_col_index = mat[mid_row].index(max(mat[mid_row]))
12
13 # Compare the maximum element with the element directly below it
14 # If current max is greater, peak must be in upper half (including mid)
15 if mat[mid_row][max_col_index] > mat[mid_row + 1][max_col_index]:
16 right = mid_row
17 # Otherwise, peak must be in lower half (excluding mid)
18 else:
19 left = mid_row + 1
20
21 # After binary search, left == right, pointing to the row containing a peak
22 # Find the column index of the maximum element in this row
23 peak_col = mat[left].index(max(mat[left]))
24
25 return [left, peak_col]
26
1class Solution {
2 /**
3 * Finds a peak element in a 2D grid using binary search on rows.
4 * A peak element is greater than or equal to its 4 neighbors (up, down, left, right).
5 *
6 * @param mat The input 2D matrix
7 * @return An array containing the row and column indices of a peak element
8 */
9 public int[] findPeakGrid(int[][] mat) {
10 int leftRow = 0;
11 int rightRow = mat.length - 1;
12 int numColumns = mat[0].length;
13
14 // Binary search on rows
15 while (leftRow < rightRow) {
16 int midRow = (leftRow + rightRow) >> 1; // Equivalent to (leftRow + rightRow) / 2
17
18 // Find the column index of the maximum element in the middle row
19 int maxColumnIndex = maxPos(mat[midRow]);
20
21 // Compare the maximum element with the element directly below it
22 // If current max is greater, the peak must be in the upper half (including midRow)
23 if (mat[midRow][maxColumnIndex] > mat[midRow + 1][maxColumnIndex]) {
24 rightRow = midRow;
25 } else {
26 // Otherwise, the peak must be in the lower half (excluding midRow)
27 leftRow = midRow + 1;
28 }
29 }
30
31 // Return the peak position: leftRow and the column with max value in that row
32 return new int[] {leftRow, maxPos(mat[leftRow])};
33 }
34
35 /**
36 * Finds the index of the maximum element in a 1D array.
37 *
38 * @param arr The input array
39 * @return The index of the maximum element
40 */
41 private int maxPos(int[] arr) {
42 int maxIndex = 0;
43
44 // Iterate through the array to find the maximum element's index
45 for (int i = 1; i < arr.length; ++i) {
46 if (arr[maxIndex] < arr[i]) {
47 maxIndex = i;
48 }
49 }
50
51 return maxIndex;
52 }
53}
54
1class Solution {
2public:
3 vector<int> findPeakGrid(vector<vector<int>>& mat) {
4 // Binary search on rows to find a peak element
5 int left = 0;
6 int right = mat.size() - 1;
7
8 while (left < right) {
9 // Calculate middle row index
10 int midRow = (left + right) >> 1; // Equivalent to (left + right) / 2
11
12 // Find the column index of the maximum element in the middle row
13 int maxColIndex = distance(mat[midRow].begin(),
14 max_element(mat[midRow].begin(), mat[midRow].end()));
15
16 // Compare the maximum element with the element directly below it
17 // If current max is greater, the peak must be in the upper half (including midRow)
18 if (mat[midRow][maxColIndex] > mat[midRow + 1][maxColIndex]) {
19 right = midRow;
20 }
21 // Otherwise, the peak must be in the lower half (excluding midRow)
22 else {
23 left = midRow + 1;
24 }
25 }
26
27 // After binary search, left == right, pointing to the row containing a peak
28 // Find the column index of the maximum element in this row
29 int peakColIndex = distance(mat[left].begin(),
30 max_element(mat[left].begin(), mat[left].end()));
31
32 // Return the coordinates of the peak element
33 return {left, peakColIndex};
34 }
35};
36
1/**
2 * Finds a peak element in a 2D grid using binary search on rows.
3 * A peak element is an element that is greater than or equal to its neighbors.
4 *
5 * @param mat - The input 2D matrix of numbers
6 * @returns An array containing the row and column indices of a peak element
7 */
8function findPeakGrid(mat: number[][]): number[] {
9 // Initialize binary search boundaries for row indices
10 let left: number = 0;
11 let right: number = mat.length - 1;
12
13 // Binary search on rows to find the peak
14 while (left < right) {
15 // Calculate the middle row index
16 const middleRow: number = (left + right) >> 1;
17
18 // Find the column index of the maximum element in the middle row
19 const maxColumnIndex: number = mat[middleRow].indexOf(Math.max(...mat[middleRow]));
20
21 // Compare the maximum element with the element directly below it
22 // If current element is greater, the peak must be in the upper half (including middle)
23 if (mat[middleRow][maxColumnIndex] > mat[middleRow + 1][maxColumnIndex]) {
24 right = middleRow;
25 } else {
26 // Otherwise, the peak must be in the lower half (excluding middle)
27 left = middleRow + 1;
28 }
29 }
30
31 // After binary search, left points to the row containing a peak
32 // Find the column index of the maximum element in that row
33 const peakRow: number = left;
34 const peakColumn: number = mat[peakRow].indexOf(Math.max(...mat[peakRow]));
35
36 return [peakRow, peakColumn];
37}
38
Time and Space Complexity
The time complexity is O(n × log m)
, where m
is the number of rows and n
is the number of columns in the matrix. The algorithm uses binary search on the rows, which takes O(log m)
iterations. In each iteration, we need to find the maximum element in the current row using max(mat[mid])
and then find its index using mat[mid].index()
. Finding the maximum requires traversing all n
elements in the row with O(n)
time, and finding the index of that maximum requires another O(n)
traversal in the worst case. Therefore, each binary search iteration has O(n)
complexity, resulting in a total time complexity of O(n × log m)
.
The space complexity is O(1)
as the algorithm only uses a constant amount of extra space for variables l
, r
, mid
, and j
, regardless of the input size.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Inefficient Maximum Element Finding
The current implementation calls max(mat[mid_row])
and then mat[mid_row].index(max(mat[mid_row]))
, which scans the row twice - once to find the maximum value and once to find its index. This is inefficient and doubles the work.
Solution:
# Instead of:
max_col_index = mat[mid_row].index(max(mat[mid_row]))
# Use:
max_col_index = 0
for j in range(1, len(mat[mid_row])):
if mat[mid_row][j] > mat[mid_row][max_col_index]:
max_col_index = j
2. Incorrect Time Complexity Analysis
The stated time complexity in the explanation is misleading. The actual complexity is O(m × n + log(m) × n)
which simplifies to O(m × n)
due to the initial scanning, not the desired O(n log m)
.
Solution:
Remove any unnecessary full matrix scans and ensure you're only examining O(log m)
rows, each taking O(n)
time to process.
3. Boundary Check Assumption
The code assumes mid_row + 1
always exists when comparing, but doesn't explicitly handle the case when mid_row
might be the last row during iteration.
Solution:
# Add explicit boundary checking:
if mid_row + 1 < len(mat):
if mat[mid_row][max_col_index] > mat[mid_row + 1][max_col_index]:
right = mid_row
else:
left = mid_row + 1
else:
# mid_row is the last row, so it could contain the peak
right = mid_row
4. Not Verifying the Peak Property
The algorithm assumes the final element found is a peak without verifying all four neighbors. While mathematically correct due to the binary search logic, this can lead to confusion during debugging or when requirements change.
Solution: Add a verification step (especially useful during development/debugging):
def is_peak(mat, i, j):
m, n = len(mat), len(mat[0])
val = mat[i][j]
# Check all four neighbors (with boundary handling)
if i > 0 and mat[i-1][j] >= val: return False
if i < m-1 and mat[i+1][j] >= val: return False
if j > 0 and mat[i][j-1] >= val: return False
if j < n-1 and mat[i][j+1] >= val: return False
return True
5. Column-wise vs Row-wise Binary Search Choice
The solution arbitrarily chooses to binary search on rows. If n << m
(many more rows than columns), it would be more efficient to binary search on columns instead.
Solution: Choose the dimension to binary search based on matrix shape:
def findPeakGrid(self, mat):
m, n = len(mat), len(mat[0])
# Choose more efficient approach based on dimensions
if m >= n:
# Binary search on rows (current approach)
return self.binarySearchRows(mat)
else:
# Binary search on columns (transposed logic)
return self.binarySearchColumns(mat)
Problem: Given a list of tasks and a list of requirements, compute a sequence of tasks that can be performed, such that we complete every task once while satisfying all the requirements.
Which of the following method should we use to solve this problem?
Recommended Readings
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!