文章目录
- 1. 最长递增子序列(300)
- 2. 摆动序列(376)
- 3. 最长递增子序列的个数(673)
- 4. 最长数对链(646)
1. 最长递增子序列(300)
题目描述:
状态表示:
根据经验以及题目要求,设置dp数组,dp[i]表示以i位置为结尾的子序列的最长长度。
状态转移方程:
这里因为涉及到序列的概念,要想获得dp[i]的值,首先需要遍历i位置之前的数组,如果数值小于i位置的数值,那么dp[i]的值就是该位置的dp数组值加一,最终dp[i]的值就是这些值中的最大值。状态转移方程可以归结为在nums[i]>nums[j]时,dp[i]=max(dp[i],dp[j]+1),这里的j取值是0到i-1,具体看代码。
初始化:
初始化直接将dp数组中的所有值都赋为1即可,因为以每个位置为结尾的递增子序列的长度是至少为1的。
填表顺序:
从左至右。
返回值:
返回dp数组最大值。
代码如下:
class Solution {
public int lengthOfLIS(int[] nums) {
int n = nums.length;
int[] dp = new int[n];
for (int i = 0; i < n; i++) {
dp[i] = 1;
}
int max = dp[0];
for (int i = 1; i < n; i++) {
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
max = Math.max(max, dp[i]);
}
return max;
}
}
题目链接
时间复杂度:O(N^2)
空间复杂度:O(N)
2. 摆动序列(376)
题目描述:
动态表示:
设置两个数组f和g,分别代表i位置的数大于上一个位置的数值以及i位置的数值小于上一个位置的数值。
动态转移方程:
这里跟子数组问题中的最长湍流数组问题是一个道理,不过在搜索前一个数值的时候要加一个循环遍历i位置之前的数值,因为这里是序列问题,而不是数组问题。当i位置数值大于上一个数值时,f[i]=g[j]+1,当i位置数值小于上一个数值时,g[i]=f[j]+1。
初始化:
初始化的话,因为单个数值就可以构成长度为1的摆动序列,所以可以直接将f和g数组直接全部初始化1。
填表顺序:
从左至右。
返回值:
返回数组g和f中的最大值。
代码如下:
class Solution {
public int wiggleMaxLength(int[] nums) {
int n = nums.length;
int[] f = new int[n];
int[] g = new int[n];
for (int i = 0; i < n; i++) {
f[i] = 1;
g[i] = 1;
}
int max = 1;
for (int i = 1; i < n; i++) {
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) {
f[i] = Math.max(f[i], g[j] + 1);
} else if (nums[i] < nums[j]) {
g[i] = Math.max(g[i], f[j] + 1);
}
}
max = Math.max(Math.max(g[i], f[i]), max);
}
return max;
}
}
题目链接
时间复杂度:O(N^2)
空间复杂度:O(N)
3. 最长递增子序列的个数(673)
题目描述:
状态表示:
这题比较特殊,分别设置两个数组count[i]以及len[i]分别表示以i位置元素为结尾的子序列的最长严格递增子序列的个数以及最长严格递增子序列的长度。
状态转移方程:
因为题目是要求序列严格递增,所以只考虑在num[i]>nums[j]时的情况,在此时如果len[j]+1>len[i]时,len[i]=len[j]+1,并且将count[i]赋为count[j],如果len[j]+1==len[i],那么len数组的值不变,但是count[i]+=count[j]。这个过程如果结合前面子序列题目的思想是很好理解的,具体看代码。
初始化:
因为本题考虑的是递增子序列,单独的一个数值元素即可构成序列,所以可以直接将count和len数组的全部元素先赋为1。
填表顺序:
从左至右。
返回值:
返回以i位置为结尾的最长递增子序列的count数组中i位置的值,但是要考虑一种情况就是len数组中可能会出现多个相同的最大值,这样就要把count数组中的对应的多个位置的值加起来,这个过程可以使用一个简单的贪心算法解决。
代码如下:
class Solution {
public int findNumberOfLIS(int[] nums) {
int n = nums.length;
int[] len = new int[n];
int[] count = new int[n];
for (int i = 0; i < n; i++) {
len[i] = 1;
count[i] = 1;
}
for (int i = 1; i < n; i++) {
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) {
if (len[i] < len[j] + 1) {
count[i] = count[j];
len[i] = len[j] + 1;
} else if (len[i] == len[j] + 1) {
count[i] += count[j];
}
}
}
}
int ret = count[0];
int max = len[0];
for (int i = 1; i < n; i++) {
if (max < len[i]) {
ret = count[i];
max = len[i];
} else if (max == len[i]) {
ret += count[i];
}
}
return ret;
}
}
题目链接
时间复杂度:O(N^2)
空间复杂度:O(N)
4. 最长数对链(646)
题目描述:
状态表示:
根据经验以及题目要求设置一个数组dp,使用dp[i]来表示以第i个位置的数对作为结尾的最长数对链的长度。
状态转移方程:
这里的状态转移方程和这篇博客介绍的第一题的思路一致,都是在遍历pairs这个主数组时再加上一个循环,但是题目给出一个额外的条件就是可以无视顺序,所以为了得到更长的递增子序列要去提前将pairs数组给排序好,因为排序的是数对,所以在使用Arrays中的静态方法sort的时候要设置一个lambda表达式来指定排序的规则。
初始化:
还是一样,先对dp数组中的每一个元素都赋为1。
填表顺序:
从左至右。
返回值:
返回dp数组中的最大值即可。
代码如下:
class Solution {
public int findLongestChain(int[][] pairs) {
int n = pairs.length;
Arrays.sort(pairs, (o1, o2) -> {
return o1[0] - o2[0];
});
int[] dp = new int[n];
for (int i = 0; i < n; i++) {
dp[i] = 1;
}
int max = dp[0];
for (int i = 1; i < n; i++) {
for (int j = 0; j < i; j++) {
if (pairs[i][0] > pairs[j][1]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
max = Math.max(max, dp[i]);
}
return max;
}
}
题目链接
时间复杂度:O(N^2)
空间复杂度:O(N)