LeetCode #845 Longest Mountain in Array 数组 线性DP

Description


Let's call any (contiguous) subarray B (of A) a mountain if the following properties hold:

  • B.length >= 3
  • There exists some 0 < i < B.length - 1 such that B[0] < B[1] < ... B[i-1] < B[i] > B[i+1] > ... > B[B.length - 1]
    (Note that B could be any subarray of A, including the entire array A.)

Given an array A of integers, return the length of the longest mountain.

Return 0 if there is no mountain.

Example 1:

Input: [2,1,4,7,3,2,5]
Output: 5
Explanation: The largest mountain is [1,4,7,3,2] which has length 5.

Example 2:

Input: [2,2,2]
Output: 0
Explanation: There is no mountain.

Note:

  • 0 <= A.length <= 10000
  • 0 <= A[i] <= 10000

Follow up:

  • Can you solve it using only one pass?
  • Can you solve it in O(1) space?



思路


解法一

由题可知,山型数组的 LIS(LDS) 部分是严格递增(递减)的,所以判断条件只能是 < 或 >,不能是 ≤ 或 ≥ 。

先不考虑什么高级解法,暴力解题。利用一个 for 循环得到每个 increasing sequence 的最后一个元素,称之为 peak,并逐一加入 peak_points 中。随后遍历 peak_points 中的每一个 peak, 继续计算以它为起点的 decreasing sequence 的长度,同时计算出山型数组最长的长度。

时间复杂度:O(n^2),因为得到了每一个 peak 之后还需要一个循环来处理 decreasing sequence
空间复杂度:O(n),需要一个 HashMap 存储 peak 的索引和以它为终点的 increasing sequence 的长度

耗时 28 ms, Memory 9.7 MB, ranking 15%

class Solution {
public:
    int longestMountain(const vector<int> &nums) {
        if (nums.size() < 3) return 0;
        
        // mapping from index of peak element to increasing sequence length
        unordered_map<int, int> peak_points;  
        int incre_seq_length = 1;
        for (int i = 1; i < nums.size(); ++i) {
            if (nums[i-1] < nums[i]) {
                ++incre_seq_length;
            } else {
                // length of increasing part of B should be at least 2
                if (incre_seq_length >= 2) {
                    peak_points[i - 1] = incre_seq_length;
                }
                
                incre_seq_length = 1;
            }
        }
        
        // derive length of decresing sequences following start_point
        int max_mountain_length = 0;
        for (auto item : peak_points) {
            int mountain_length = item.second;
            bool has_decre_element = false;
            
            for (int i = item.first + 1; i < nums.size(); ++i) {
                if (nums[i-1] > nums[i]) {
                    if (!has_decre_element) has_decre_element = true;
                    ++mountain_length;
                } else {
                    break;
                }
            }
            
            if (has_decre_element && 
                    max_mountain_length < mountain_length) {
                max_mountain_length = mountain_length;
            }
        }
        
        return max_mountain_length;
    }
};



解法二

利用 DP 求 LIS 的思想求解,逆向求 LIS(相当于求出了一个山型数组的LDS),然后再正向求 LIS,在正向求解时顺便也计算出 mountain_length 。

利用 up[] 和 down[] 记录了求解 LIS 和 LDS 时每个子序列的长度。up[i] 表示以 nums[i] 为结尾的 increasing sequence 的长度(实际为长度减一,以便计算山型数组的长度),down[i] 表示以 nums[i] 为开头的 decreasing sequence 的长度。

时间复杂度:O(n),因为求解 LDS 和 LIS 是线性DP
空间复杂度:O(n),需要用到 up[] 和 down[] 两个记录数组

耗时 20 ms, Memory 9.0 MB, ranking 91.26%

class Solution {
public:
    int longestMountain(const vector<int> &nums) {
        if (nums.size() < 3) return 0;
        
        int max_mountain_length = 0;
        // up[i] represents length - 1 of increasing sequence that end with up[i]
        vector<int> up(nums.size());
        // down[i] represents length - 1 of decreasing sequence that start with down[i]
        vector<int> down(nums.size());
        
        for (int i = nums.size() - 2; i >= 0; --i) {
            if (nums[i] > nums[i+1]) {
                down[i] = down[i+1] + 1;
            }
        }
        
        for (int i = 1; i < nums.size(); ++i) {
            if (nums[i-1] < nums[i]) {
                up[i] = up[i-1] + 1;
                
                if (down[i] > 0) {
                    int mountain_length = up[i] + down[i] + 1;
                    if (max_mountain_length < mountain_length) {
                        max_mountain_length = mountain_length;
                    }
                }
            }
        }
        
        return max_mountain_length;
    }
};



解法三

follow up 中需要将 space 优化成 O(1),所以不能使用解法二中提到的记录数组 up[] 和 down[]。
因为每次更新状态时,up[] 和 down[] 中的值不能直接被几个变量替代,所以不能直接压缩空间为 O(1),而是需要重新设计 DP 算法里的状态。

用 up 表示以 nums[i] 为结尾的 increasing sequence 的长度(实际为长度减一,以便计算山型数组的长度),用 down 表示以 nums[i] 为结尾的 decreasing sequence 的长度。这样,山型数组的长度就等价于 up + down + 1。

因为 nums 的第一个元素肯定不是 peak (山型数组至少要三个数据,如果第一个元素是 peak,那就只剩山型数组的右半部了),所以从 i=1 开始遍历 nums,计算 up、down 和山型数组长度,在每一次重新爬坡时把 up 和 down 重置为 0。遍历结束后,我们就得到了山型数组最长的长度。

时间复杂度:O(n)
空间复杂度:O(1)

耗时 20 ms, Memory 7.8 MB, ranking 91.26%

class Solution {
public:
    int longestMountain(const vector<int> &nums) {
        if (nums.size() < 3) return 0;
        
        int max_mountain_length = 0;
        // up represents length - 1 of increasing sequence that end with current idx
        int up = 0;
        // down represents length - 1 of decreasing sequence that start with current idx
        int down = 0;
        
        // nums[0] cannot be a peak element
        for (int i = 1; i < nums.size(); ++i) {
            if (nums[i-1] == nums[i]) {  // go with a new mountain sequence
                up = 0;
                down = 0;
                continue;
            }
            
            if (nums[i-1] < nums[i]) {
                if (down > 0) {  // go with a new mountain sequence
                    up = 0;
                    down = 0;
                }
                
                ++up;
            }
            
            if (nums[i-1] > nums[i]) {
                ++down;
            }
            
            if (up > 0 && down > 0) {
                int mountain_length = up + down + 1;
                if (max_mountain_length < mountain_length) {
                    max_mountain_length = mountain_length;
                }
            }
        }
        
        return max_mountain_length;
    }
};



参考




原文地址:https://www.cnblogs.com/Bw98blogs/p/12686673.html