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;
}
};
参考
- 《[LeetCode] 845. Longest Mountain in Array 数组中最长的山》:https://www.cnblogs.com/grandyang/p/10459400.html