Loading

数组中的最长山脉

1.问题描述

我们把数组 A 中符合下列属性的任意连续子数组 B 称为 “山脉”

  • B.length >= 3
  • 存在 0 < i < B.length - 1 使得 B[0] < B[1] < ... B[i-1] < B[i] > B[i+1] > ... > B[B.length - 1]

(注意:B 可以是 A 的任意子数组,包括整个数组 A。)

给出一个整数数组 A,返回最长 “山脉” 的长度。

如果不含有 “山脉” 则返回 0

示例 1:

输入:[2,1,4,7,3,2,5]
输出:5
解释:最长的 “山脉” 是 [1,4,7,3,2],长度为 5。

示例 2:

输入:[2,2,2]
输出:0
解释:不含 “山脉”。

提示:

  1. 0 <= A.length <= 10000
  2. 0 <= A[i] <= 10000

2.求解

暴力法枚举山顶

  • 枚举数组[1,length-1]的元素,对于每个元素,判断其是否能向左右延伸成为山脉,最后的右延伸距离减去左延伸距离+1即为当前山顶的长度

代码如下

/*
 * 执行用时:4 ms, 在所有 Java 提交中击败了22.60% 的用户
 * 内存消耗:39.5 MB, 在所有 Java 提交中击败了83.89% 的用户
 * */
public int longestMountain(int[] A) {
    int left, right;
    int maxAns = 0;
    //枚举每一个可能是山顶的点
    for (int i = 1; i < A.length - 1; i++) {
        int ans;
        int pre = A[i];
        left = i;
        right = i;
        //判断是否能成为山顶(左右两侧是否小于它)
        if (A[i - 1] < pre && A[right + 1] < pre) {
            left = i - 1;
            right = i + 1;
            //判断左侧山脚
            while (left > 0 && A[left] < pre) {
                pre = A[left];
                left--;
            }
            if (A[left] >= pre) left++; //修正左侧山脚位置
            pre = A[i];
            //判断右侧山脚
            while (right < A.length - 1 && A[right] < pre) {
                pre = A[right];
                right++;
            }
        }
        if (A[right] >= pre) right--;   //修正右侧侧山脚位置
        ans = right - left + 1;
        maxAns = Math.max(ans, maxAns); //计算山脉总长度
    }
    return maxAns;
}
  • 时间复杂度平方级,执行用时较长

动态规划

  • 新建一个大小等于A数组长度的数组left,left数组内部元素初始值都为0
  • left[i]来表示A[i]最多能向左侧扩展的长度,如果A[i] > A[i-1]则表示A[i]可以向左侧延伸到A[i-1],所以有left[i] = left[i-1] + 1
  • 同样我们使用数组right[i]来表示A[i]最多能向右侧扩展的长度,只不过这次是从右侧倒序遍历
  • 最后我们遍历每一个可能是山顶的位置A[i],计算它向左侧和右侧延伸的距离和即可

代码如下

/*
 * 执行用时:3 ms, 在所有 Java 提交中击败了69.39% 的用户
 * 内存消耗:38.8 MB, 在所有 Java 提交中击败了100.00% 的用户
 * */
public int longestMountain(int[] A) {
    int len = A.length;
    if (len == 0) {
        return 0;
    }
    //对于每个A[i],记录下能够向左延伸到的距离
    int[] left = new int[len];
    for (int i = 1; i < len; i++) {
        left[i] = A[i - 1] >= A[i] ? 0 : left[i - 1] + 1;
    }
    //对于每个A[i],记录下能够向右延伸到的距离
    int[] right = new int[len];
    for (int i = len - 2; i >= 0; i--) {
        right[i] = A[i + 1] >= A[i] ? 0 : right[i + 1] + 1;
    }
    //对于每个可能成为山顶的位置,判断是能否成为山脉  
    int maxAns = 0;
    for (int i = 1; i < len - 1; i++) {
        //若是山脉,则计算山脉的长度
        if (left[i] > 0 && right[i] > 0) {
            maxAns = Math.max(maxAns, left[i] + right[i] + 1);
        }
    }
    return maxAns;
}
  • 时间复杂度:O(n),其中 n是数组 AAA 的长度。

  • 空间复杂度:O(n),即为数组 left 和 right 需要使用的空间。

双指针枚举山脚位置

  • 定义指针left、right,固定left位置,每次向右移动right指针,判断能否成为山脉
  • 对于left的情况分析
    1. A[left] > A[left + 1],不构成左侧山脚,令left = right,右侧山脚成为下一次枚举左侧山脚的起点
    2. 1判断通过,证明存在左侧山脚,移动right指针延伸左侧山脚
      1. 存在右侧山脚,即A[right] > A[right+1],移动right指针延伸右侧山脚,完毕后判断山脉长度,即当前山脉长度 = right - left + 1
      2. 不存在右侧山脚,因为right = left + 1,说明当前right所在的位置也不可能成为左侧山脚,right指针右移一位,令left = right,右侧山脚成为下一次枚举左侧山脚的起点

代码如下

/*
 * 执行用时:2 ms, 在所有 Java 提交中击败了99.84% 的用户
 * 内存消耗:39.7 MB, 在所有 Java 提交中击败了75.75% 的用户
 * */
public int longestMountain(int[] A) {
    int n = A.length;
    int left = 0;
    int maxAns = 0;
    //少于3个点不构成山脉
    while (left + 2 < n) {
        int right = left + 1;
        //判断是否构成左侧山脚
        if (A[left] < A[left + 1]) {
            //延伸左侧山脚
            while (right + 1 < n && A[right] < A[right + 1]) {
                right++;
            }
            //判断是否构成右侧山脚
            if (right + 1 < n && A[right] > A[right + 1]) {
                //延伸右侧山脚
                while (right + 1 < n && A[right] > A[right + 1]) {
                    right++;
                }
                //所有条件满足,构成山脉,判断山脉长度
                maxAns = Math.max(maxAns, right - left + 1);
            } else {
                right++;
            }
        }
        //从右侧山脚开始判断是否是下一个左侧山脚
        left = right;
    }
    return maxAns;
}
  • 时间复杂度:O(n),其中 n 是数组 A 的长度。

  • 空间复杂度:O(1)。

posted @ 2020-10-25 15:43  水纸杯  阅读(389)  评论(0)    收藏  举报