https://leetcode-cn.com/problems/longest-increasing-subsequence/
这道题是动态规划的典型例题,有
O
2
O^2
O2和
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)两种解法。
解法一
O
(
n
2
)
O(n^2)
O(n2)的解法比较容易想到,要找最长的上升子序列,我们维护一个dp[n]数组,用dp[i]来表示以第i个元素为结尾的最长上升子序列长度,例题中dp[0] = 1, dp[3] = 2。如何更新这个数组呢?
按照定义,我们肯定要更新所有的dp[i],因此要遍历整个数组一次,当遍历到nums[i]时,要利用前面的数据来更新dp[i]:
- 子序列是上升的,那么肯定要找
nums[j] < nums[i] - 要维护最长的子序列,那么就要找最大的
dp[j],最终dp[i] = dp[j] + 1
最终答案自然就是最大的那个dp[i]。
代码
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int dp[nums.size()]; // dp[i]:以第i个数字为结尾的最长上升子序列长度
int result = 0;
for (int i = 0; i < nums.size(); ++i) {
int max_dp = 0;
for (int j = 0; j < i; ++j) {
if (nums[j] < nums[i] && dp[j] > max_dp) max_dp = dp[j];
}
dp[i] = max_dp + 1;
result = max(result, dp[i]);
}
return result;
}
};
复杂度分析
算法遍历数组一次需要
O
(
n
)
O(n)
O(n),每次更新dp[i]时又要遍历所有的j < i,需要
O
(
n
)
O(n)
O(n),因此总的时间复杂度为
O
(
n
2
)
O(n^2)
O(n2),空间复杂度为
O
(
n
)
O(n)
O(n)。
解法二
看到时间复杂度要求中的这个
l
o
g
n
logn
logn,我们知道要用到二分法来解决这个问题。如果继续采用解法一中的dp数组,是没法用二分法的,换言之,解法一的时间复制度没法优化。
重新定义dp数组,令dp[i]代表长度为i的最长上升子序列的末尾元素,如果有多个这种元素,取值最小的那个(不是下标)。根据定义,我们可以得知dp[i]是关于i单调递增的,证明如下:
- 对于
dp[j] > dp[i],如果i > j,那么删除掉i对应的子序列的末尾i - j个元素,得到长度为j的最长上升子序列,设其末尾元素为num。易知num < dp[i] < dp[j],那么dp[j]就不是长度为j的最长上升子序列的末尾元素中最小的那个,与定义不符,因此i < j。
定义len为最长上升子序列长度,初始时len = 1, dp[1] = nums[0]。
同样地,我们先遍历整个数组来更新dp,对于遍历到的nums[i]:
- 如果
nums[i] > dp[len],则把nums[i]加入到这个子序列的末尾,dp[++len] = nums[i]。 - 否则,
nums[i]不会导致子序列的长度增加,但会导致前面的dp的更新。对于j < len,我们找到dp[j - 1] < nums[i] < dp[j],更新dp[j] = nums[i],即找到第一个大于nums[i]的dp[j],更新其值。
上述第二点可以用二分查找来完成,因为dp数组是单调递增的。
最终答案即为len。
代码
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
if (nums.size() == 0) return 0;
int dp[nums.size() + 1]; // dp[i]: 长度为i的最长上升子序列的最末元素的最小值
int len = 1;
dp[1] = nums[0];
for (int i = 0; i < nums.size(); ++i) {
if (nums[i] > dp[len]) dp[++len] = nums[i];
else {
int left = 1, right = len;
while (left <= right) {
int mid = (left + right) / 2;
if (dp[mid] >= nums[i]) right = mid - 1;
else left = mid + 1;
}
dp[left] = nums[i];
}
}
return len;
}
};
复杂度分析
时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn),空间复杂度 O ( n ) O(n) O(n)。
本文介绍了解决最长上升子序列问题的两种算法,一种是O(n^2)的动态规划解法,另一种是优化至O(nlogn)的二分查找解法。通过详细解释和代码示例,展示了如何寻找数组中最长的连续递增子序列。

被折叠的 条评论
为什么被折叠?



