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)。