Peak Finding

问题描述

一维的情形为例,Peak Finding 描述这样一个问题。

给定一个数组 a,是否能够找到这样一个坐标 i,使得 \(a[i - 1] \le a[i] \le a[i + 1]\),这样的坐标 i 被称为 peak

特别地,在边界上的元素只需要保证大于等于与其相邻的一个元素,也被判定为 peak


Straightforward Algorithm

遍历数组,找到是否存在一个位置上的元素,该元素大于等于与其所有的相邻元素。

复杂度 \(\Theta(N)\)。

def peak_finding(a):
    if len(a) == 0:
        return None
    if len(a) == 1 or a[0] >= a[1]:
        return 0
    if a[-1] >= a[-2]:
        return len(a) + 1
    for i in range(1, len(a) - 1):
        left = a[i - 1]
        right = a[i + 1]
        if left <= a[i] <= right:
            return i

Divide and Conquer

分而治之算法。考察 \(a[n / 2]\) 及与其相邻的元素。

  • 如果 \(a[n / 2]\) 小于其邻居,则可以在左侧的子数组中递归寻找 peak
  • 如果 \(a[n / 2]\) 小于其邻居,则可以在右侧的子数组中递归寻找 peak
  • 如果以上两种情形均不成立,则 \(n / 2\) 就是一个 peak
def peak_finding(a):
    if len(a) == 0:
        return None
    return divide_and_conquer(a, 0, len(a) - 1)

def divide_and_conquer(a, low, high):
    if low >= high:
        return low
    middle = (low + high) // 2
    if a[middle - 1] > a[middle]:
        return divide_and_conquer(a, low, middle - 1)
    elif a[middle + 1] > a[middle]:
        return divide_and_conquer(a, middle + 1, high)
    else:
        return middle

假设 \(T(N)\) 是该算法运行的时间,则有

\[T(N) = T(N / 2) + \Theta(1) = \underbrace{\Theta(1) + \dots + \Theta(1)}_{log(N)} = \Theta(logN) \]


问题扩展

Peak Finding

问题扩展至二维情形。如上图,当且仅当

\[a \ge x, x \in \{b, c, d, e\} \]

成立时,\(a\) 所在的位置称为 peak

尝试找出二维情形下的一个 peak


Greedy Ascent Algorithm

从数组的任意位置开始,向数组元素增大的方向移动,直至周围的数组元素均小于当前元素。

复杂度 \(\Theta(MN)\)。

def peak_finding(a):
    if len(a) == 0 or len(a[0]) == 0:
        return None
    if len(a) == 1 or len(a[0]) == 1:
        return (0, 0)
    n = len(a)
    m = len(a[0])
    location = (n // 2, m // 2)
    while True:
        row, column = location
        value = a[row][column]
        if row > 0 and a[row - 1][column] > value:
            location = (row - 1, column)
            continue
        if row < n - 1 and a[row + 1][column] > value:
            location = (row + 1, column)
            continue
        if column > 0 and a[row][column - 1] > value:
            location = (row, column - 1)
            continue
        if column < m - 1 and a[row][column + 1] > value:
            location = (row, column + 1)
            continue
        return location

Divide and Conquer

分而治之算法。考察第 \(j = m / 2\) 列。

  • 找出这一列上最大的元素 \(a[i][j]\),将其与其左右相邻的元素比较
  • 如果 \(a[i][j - 1] > a[i][j]\),则可以在左侧的列中递归寻找二维 peak
  • 如果 \(a[i][j + 1] > a[i][j]\),则可以在右侧的列中递归寻找二维 peak
  • 如果以上两种情形均不成立,则 \((i, j)\) 就是一个二维 peak
def peak_finding(a):
    if len(a) == 0 or len(a[0]) == 0:
        return None
    m = len(a[0])
    return divide_and_conquer(a, 0, m - 1)

def divide_and_conquer(a, low, high):
    if low >= high:
        row_index = find_max_row_on_column(a, low)
        return row_index, low
    middle = (low + high) // 2
    row_index = find_max_row_on_column(a, middle)
    if a[row_index][middle - 1] > a[row_index][middle]:
        return divide_and_conquer(a, low, middle - 1)
    elif a[row_index][middle + 1] > a[row_index][middle]:
        return divide_and_conquer(a, middle + 1, high)
    else:
        return row_index, middle

def find_max_row_on_column(a, column):
    max_row = 0
    for i in range(len(a)):
        if a[i][column] > a[max_row][column]:
            max_row = i
    return max_row

假设 \(T(N, M)\) 是该算法运行的时间,则有

\[T(N, M) = T(N, M / 2) + \Theta(N) = \underbrace{\Theta(N) + \dots + \Theta(N)}_{log(M)} = \Theta(NlogM) \]

上一篇:剑指 Offer 11. 旋转数组的最小数字


下一篇:34. 在排序数组中查找元素的第一个和最后一个位置(二分法)