数组中的最长山脉

标签: 数组 双指针 动态规划 枚举

难度: Medium

把符合下列属性的数组 arr 称为 山脉数组

  • arr.length >= 3
  • 存在下标 i0 < i < arr.length - 1),满足
    • arr[0] < arr[1] < ... < arr[i - 1] < arr[i]
    • arr[i] > arr[i + 1] > ... > arr[arr.length - 1]

给出一个整数数组 arr,返回最长山脉子数组的长度。如果不存在山脉子数组,返回 0

示例 1:

输入:arr = [2,1,4,7,3,2,5]
输出:5
解释:最长的山脉子数组是 [1,4,7,3,2],长度为 5。

示例 2:

输入:arr = [2,2,2]
输出:0
解释:不存在山脉子数组。

提示:

  • 1 <= arr.length <= 104
  • 0 <= arr[i] <= 104

进阶:

  • 你可以仅用一趟扫描解决此问题吗?
  • 你可以用 O(1) 空间解决此问题吗?

Submission

运行时间: 35 ms

内存: 16.9 MB

class Solution:
    def longestMountain(self, arr: List[int]) -> int:
        size = len(arr)
        res = 0
        for i in range(1, size - 1):
            if arr[i] > arr[i - 1] and arr[i] > arr[i + 1]:
                left = i - 1
                right = i + 1

                while left > 0 and arr[left - 1] < arr[left]:
                    left -= 1
                while right < size - 1 and arr[right + 1] < arr[right]:
                    right += 1
                if right - left + 1 > res:
                    res = right - left + 1
        return res

Explain

该题解采用单遍扫描数组的方式来识别并计算最长的山脉子数组的长度。从数组的第二个元素开始,直到倒数第二个元素结束,判断每个元素是否是山脉的峰顶(即当前元素比左右两边的元素都要大)。若是,则分别向左和向右扩展,直到不满足上升或下降的条件,从而确定整个山脉的边界。最后,计算当前山脉的长度,并与已记录的最长长度进行比较,更新最长长度。

时间复杂度: O(n)

空间复杂度: O(1)

class Solution:
    def longestMountain(self, arr: List[int]) -> int:
        size = len(arr)  # 数组的长度
        res = 0  # 存储最长山脉的长度
        for i in range(1, size - 1):  # 从第二个元素到倒数第二个元素
            if arr[i] > arr[i - 1] and arr[i] > arr[i + 1]:  # 判断是否为山峰
                left = i - 1  # 初始化左边界
                right = i + 1  # 初始化右边界

                while left > 0 and arr[left - 1] < arr[left]:  # 向左扩展直到不再上升
                    left -= 1
                while right < size - 1 and arr[right + 1] < arr[right]:  # 向右扩展直到不再下降
                    right += 1
                if right - left + 1 > res:  # 更新最长山脉的长度
                    res = right - left + 1
        return res  # 返回最长的山脉长度

Explore

在数组中,山脉的定义是一个先上升后下降的序列,因此山脉至少需要三个元素组成。一个元素要成为山脉的峰顶,它必须比它的左右两边的元素都大,这意味着第一个和最后一个元素无法满足这一条件,因为第一个元素没有左侧元素,最后一个元素没有右侧元素。因此,检查是否为山峰的过程只需要从第二个元素开始到倒数第二个元素结束。

该算法的设计是基于找到明确的山峰(即一个元素同时大于其左右元素)然后扩展以确定山脉的完整长度。这种方法不会错过任何合法的山脉子数组,因为每一个合法的山脉都必须有一个明确的山峰。只要有山峰出现,无论山脉多短,都会通过向左右扩展被正确识别出来。

连续相等的元素不会构成山脉的一部分,因为山脉的定义要求有明确的上升和下降趋势,而相等的元素表示没有趋势变化。在算法中,这些连续相等的元素会阻断山脉的形成。例如,在向左或向右扩展时,一旦遇到相等的元素,就会停止扩展。因此,连续相等的元素会阻止形成有效的山峰,并确保只有真正的上升和下降序列被计算为山脉。

在向左和向右扩展时,条件 `left > 0` 和 `right < size - 1` 确保了数组索引不会越界。向左扩展时,必须确保 `left` 位于数组的有效索引范围内(即大于0),这样才能安全地访问 `arr[left - 1]`;同理,向右扩展时,`right` 必须小于数组的最大索引(即 `size - 1`),以便安全地访问 `arr[right + 1]`。这些边界条件是为了保证程序不会因为数组越界而发生错误。