题目来源:300. 最长递增子序列、673. 最长递增子序列的个数
大致题意:
前者就是求出最长递增子序列,后者就是求出最长递增子序列有多少种(只求个数)
思路
因为每日一题是求最长递增子序列的个数,所以就先做了最长上升子序列(LIS)
LIS 动态规划
使用一维数组 dp[i] 表示从首位元素到第 i 位,以 i 位置元素为结尾的 LIS 长度
- 外层从 0 - n-1 遍历,每次先初始化当前 dp[i] 为 1(最小的 LIS 也为1)
- 内层遍历从 0 到 i 位置之前的所有元素,若当前位置 j 元素值小于 i 位置元素且 dp[j] + 1 大于 dp[i],即代表以元素 j 为结尾的 LIS 加上元素 i 后的长度之前的 dp[i],那么更新 dp[i] 为 dp[j] + 1
- 每次遍历结束,对比当前已存的最大长度和 dp[i],取最大
代码:
class Solution {
public int lengthOfLIS(int[] nums) {
int maxLen = 0;
int n = nums.length;
int[] dp = new int[n];
for (int i = 0; i < n; i++) {
// 每次遍历开始先对当前位置进行初始化
dp[i] = 1;
for (int j = 0; j < i; j++) {
// 若大于之前某个元素值
if (nums[i] > nums[j]) {
// 取最长的上升子序列
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
// 更新最大长度
maxLen = Math.max(dp[i], maxLen);
}
return maxLen;
}
}
时间复杂度 O(n^2), 空间复杂度 O(n)
LIS 贪心 + 二分
使用贪心的思维,如果我们想要 LIS 尽可能的长,那么就需要使其上升的尽可能的慢,因为上升的越慢,添加新的元素就相对较小。
- 我们使用一个数组 greedy 存下当前最长上升子序列对应的元素,并且用 idx 存下当前的元素数量,也就是最长上升子序列长度
- 初始时,greedy 数组中只有首元素
- 遍历数组剩余元素,每次比较当前元素与 greedy 末尾元素的大小,若其大于末尾元素,那么 idx+1,将当前元素插入 greedy 数组末尾;否则,二分查找满足 num[j-1] < 当前元素 < num[j] 的下标 j,将 j 位置元素替换为当前元素
- 最后 idx 值就是 LIS 长度,greedy 就是满足贪心要求的 LIS
代码:
class Solution {
public int lengthOfLIS(int[] nums) {
int n = nums.length;
int[] g = new int[n + 1];
g[1] = nums[0]; // 初始化
int idx = 1;
for (int i = 1; i < n; i++) {
// 大于末尾元素,插入末尾
if (nums[i] > g[idx]) {
g[++idx] = nums[i];
}
// 小于末尾,二分查找插入位置
else {
int l = 1;
int r = idx;
while (l < r) {
int mid = (l + r) / 2;
if (nums[i] > g[mid]) {
l = mid + 1;
}
else {
r = mid;
}
}
// 更新对应位置元素值
g[l] = nums[i];
}
}
return idx;
}
}
时间复杂度 O(n logn), 空间复杂度 O(n)
LIS个数 动态规划
这个是刚刚 LIS 动态规划的升级版。
首先,需要使用两个一维数组: dp[i] 记下以 i 结尾的 LIS 长度、count[i] 记下长度和以当前数为结尾的LIS一样的上升子序列的个数
需要在更新 dp[i] 和最大长度的时候做一些额外处理。
更新 dp[i],也就是当前元素值大于之前的某个元素值的时候,有:
- 若 dp[j] + 1 大于 dp[i],更新 dp[i] 的时候,将 count[i] 更新为 count[j](因为当前的LIS,是以 j 结尾的 LIS 加上当前元素形成的,那么之前的长度有多少个,就有多少个可以加上当前元素形成新的 LIS)
- 若 dp[j] + 1 等于 dp[i],代表有长度相同但是结尾元素是 j 位置元素的 LIS,那么将 count[j] 加入 count[i](即对应长度的 LIS 个数相加)
更新最大长度,即每次 dp[i] 确定后,有:
- 若 dp[i] 大于已有的最大长度,那么更新最大长度和长度对应的个数
- 若 dp[i] 等于已有的最大长度,那么将 count[i] 加入最大长度对应的个数
代码:
class Solution {
public int findNumberOfLIS(int[] nums) {
int n = nums.length;
int ans = 0;
int maxLen = 1;
// 存以当前数为结尾的最长上升子序列长度
int[] dp = new int[n];
// 存长度和以当前数为结尾的最长上升子序列一样的上升子序列的个数
int[] count = new int[n];
for (int i = 0; i < n; i++) {
// 初始化为 1
dp[i] = 1;
count[i] = 1;
for (int j = 0; j < i; j++) {
// 当前数大于之前的某个数
if (nums[i] > nums[j]) {
// 若该数 +1 的长度大于当前的长度,则更新长度和对应的长度个数
if (dp[j] + 1 > dp[i]) {
dp[i] = dp[j] + 1;
count[i] = count[j];
}
// 若该数 +1 的长度等于当前的长度,则将对应的长度个数累加
else if (dp[j] + 1 == dp[i]) {
count[i] += count[j];
}
}
}
// 若当前统计的最长长度大于之前的最长长度,更新 ans 和 最大长度
if (dp[i] > maxLen) {
maxLen = dp[i];
ans = count[i];
}
// 若当前统计的最长长度等于之前的最长长度,将 count[i] 累加入 ans
else if (dp[i] == maxLen) {
ans += count[i];
}
}
return ans;
}
}
时间复杂度 O(n^2), 空间复杂度 O(n)