Skip to content

Two Dimensional Peak Finding

Find a peak element in a 2D grid using divide and conquer.

Two dimensional peak finding finds a position in a matrix whose value is at least as large as its neighbors in four directions.

Given a matrix AA of size n×mn \times m, a position (i,j)(i, j) is a peak if:

  • A[i][j]A[i1][j]A[i][j] \ge A[i-1][j]
  • A[i][j]A[i+1][j]A[i][j] \ge A[i+1][j]
  • A[i][j]A[i][j1]A[i][j] \ge A[i][j-1]
  • A[i][j]A[i][j+1]A[i][j] \ge A[i][j+1]

Out of bounds neighbors are treated as -\infty.

Problem

Given a matrix AA, find a peak position (i,j)(i, j).

Key Idea

Use divide and conquer on columns:

  1. Pick the middle column
  2. Find the maximum element in that column
  3. Compare it with its left and right neighbors
  4. Move to the half that contains a larger neighbor

This reduces the search space in one dimension.

Algorithm

peak_finding_2d(A):
    n = number of rows
    m = number of columns

    l = 0
    r = m - 1

    while l <= r:
        mid = (l + r) // 2

        # find row index of maximum element in column mid
        max_row = 0
        for i from 1 to n - 1:
            if A[i][mid] > A[max_row][mid]:
                max_row = i

        left = A[max_row][mid - 1] if mid > 0 else -inf
        right = A[max_row][mid + 1] if mid < m - 1 else -inf

        if A[max_row][mid] >= left and A[max_row][mid] >= right:
            return (max_row, mid)
        elif left > A[max_row][mid]:
            r = mid - 1
        else:
            l = mid + 1

    return (-1, -1)

Example

Let

A=[1081010 14131211 1591121 16171920] A = \begin{bmatrix} 10 & 8 & 10 & 10 \ 14 & 13 & 12 & 11 \ 15 & 9 & 11 & 21 \ 16 & 17 & 19 & 20 \end{bmatrix}

One possible peak is:

(2,0) with value 15 (2, 0) \text{ with value } 15

Another is:

(3,2) with value 19 (3, 2) \text{ with value } 19

The algorithm may return any valid peak.

Correctness

At each step, the algorithm considers the maximum element in the middle column.

If this element is greater than or equal to its horizontal neighbors, it is a peak because it is also the largest in its column.

If a neighbor on one side is larger, then there must be a peak in that direction. This follows from the same reasoning as in one dimensional peak finding: moving toward a larger neighbor eventually leads to a peak.

The algorithm discards half of the columns each iteration while maintaining that a peak exists in the remaining region.

Complexity

Let nn be rows and mm be columns.

operationcost
find column maxO(n)O(n)
iterationsO(logm)O(\log m)
totalO(nlogm)O(n \log m)

Space:

O(1) O(1)

Variants

variantidea
row-baseddivide by rows instead of columns
recursiveexplicit divide and conquer recursion
full 2D binarymore complex but not necessary

Comparison

dimensionmethodtime
1Dpeak findingO(logn)O(\log n)
2Dcolumn divideO(nlogm)O(n \log m)

When to Use

  • Finding local maxima in grids
  • Image processing
  • Terrain analysis
  • Optimization on discrete surfaces

Notes

  • The returned peak is not necessarily global maximum
  • Works for any rectangular matrix
  • Can be extended to higher dimensions

Implementation

def peak_finding_2d(a):
    if not a or not a[0]:
        return -1, -1

    n, m = len(a), len(a[0])
    l, r = 0, m - 1

    while l <= r:
        mid = (l + r) // 2

        max_row = 0
        for i in range(n):
            if a[i][mid] > a[max_row][mid]:
                max_row = i

        left = a[max_row][mid - 1] if mid > 0 else float('-inf')
        right = a[max_row][mid + 1] if mid < m - 1 else float('-inf')

        if a[max_row][mid] >= left and a[max_row][mid] >= right:
            return max_row, mid
        elif left > a[max_row][mid]:
            r = mid - 1
        else:
            l = mid + 1

    return -1, -1
import "math"

func PeakFinding2D(a [][]int) (int, int) {
    if len(a) == 0 || len(a[0]) == 0 {
        return -1, -1
    }

    n, m := len(a), len(a[0])
    l, r := 0, m-1

    for l <= r {
        mid := (l + r) / 2

        maxRow := 0
        for i := 0; i < n; i++ {
            if a[i][mid] > a[maxRow][mid] {
                maxRow = i
            }
        }

        left := math.MinInt
        if mid > 0 {
            left = a[maxRow][mid-1]
        }

        right := math.MinInt
        if mid < m-1 {
            right = a[maxRow][mid+1]
        }

        if a[maxRow][mid] >= left && a[maxRow][mid] >= right {
            return maxRow, mid
        } else if left > a[maxRow][mid] {
            r = mid - 1
        } else {
            l = mid + 1
        }
    }

    return -1, -1
}