Subsequence的定义: 在一个数组或字符串中的非连续子集 最经典的DP
300. Longest Increasing Subsequence
- 题目: 给出一个数组, 找出非连续的递增序列
- 思路:
- O(n^2):使用一个一维的dp数组 + 两个for循环遍历, dp数组中存的是以当前位置结尾的最长子序列长度,先用一个for loop遍历所有数字,当 遍历每个数字的时候用再另一个for循环遍历之前的所有数字,如果当前位置
i
的数字大于之前位置j
的数字并且当前的序列长度小于之前的序列长度 +1即dp[i] < dp[j] + 1
, 就将dp[i]
更新为dp[j] + 1
- O(nlogn): 使用binary search优化
- O(n^2):使用一个一维的dp数组 + 两个for循环遍历, dp数组中存的是以当前位置结尾的最长子序列长度,先用一个for loop遍历所有数字,当 遍历每个数字的时候用再另一个for循环遍历之前的所有数字,如果当前位置
//02.13
class Solution {
public int lengthOfLIS(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int max = 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] < dp[j] + 1) {
dp[i] = dp[j] + 1;
}
}
max = Math.max(dp[i], max); // max的更新必须在第二个循环的外面,否则只有一个元素的corner case过不了
}
return max;
}
}
复制代码
O(nlogn): 使用二分查找进行搜索 first number > num dp数组存的是以index长度+1的最长子序列的最小数值number 先要用Integer.MAX_VALUE将所有的
//02.13
class Solution {
public int lengthOfLIS(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int n = nums.length;
int[] dp = new int[n + 1];
for (int i = 1; i <= n; i++) {
dp[i] = Integer.MAX_VALUE; //先设定为特殊值便于最后使用for loop进行筛选
}
for (int i = 0; i < n; i++) {
int index = binarySearch(dp, nums[i]);
dp[index] = nums[i];
}
for (int i = nums.length; i >= 1; i--) {
if (dp[i] != Integer.MAX_VALUE) {
return i;
}
}
return 0;
}
private int binarySearch(int[] nums, int target) {
int start = 0;
int end = nums.length - 1;
while (start + 1 < end) {
int mid = (end - start) / 2 + start;
if (nums[mid] >= target) { //这里必须要加上等于时的条件,因为重复元素必须向前跳过
end = mid;
} else {
start = mid;
}
}
return end;
}
}
复制代码
follow up: 将最长的递增子序列打印出来 直接倒序遍历dp数组,其中的dp[i]如果不等于Integer.MAX_VALUE就表示是最长子序列中的元素
334. Increasing Triplet Subsequence[Medium]
- 题目: 给出一个无序的数组,判断是否存在一个长度为3递增子序列,要求是O(n)时间复杂度
- 思路: 位置也很重要,for 循环遍历数组,遍历到某个位置时,向后找,看能否找到两个递增的比他大的序列,
如果是最,直接更新为最小,位置信息存在于 first 和 sec转换之间的关系。
class Solution {
public boolean increasingTriplet(int[] nums) {
if (nums == null || nums.length == 0) {
return false;
}
int first = Integer.MAX_VALUE;
int sec = Integer.MAX_VALUE;
for (int num : nums) {
if (num <= first) {
first = num;
} else if (num < sec) {
sec = num;
} else if (num > sec) {
return true;
}
}
return false;
}
}
复制代码
673. Number of Longest Increasing Subsequence[Medium]
- 题目: 找出一个数组中最长子序列的个数
- 思路: 用一个LIS中的DP数组先找到最长子序列,再遍历一次数组来找到长度等于最长,在同一个位置结尾的话会出现漏加 为了求解得到正确结果,需要用到的信息包括 子序列的长度,子序列的 开两个数组,分别记录长度和个数, 递推关系: 当前位置是新发现的最长序列 当前位置是已经被发现的最长序列
//02.14
class Solution {
public int findNumberOfLIS(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int n = nums.length;
int[] dp = new int[n];
int[] count = new int[n];
int max = 1; // max初始值不能设为0, 因为当所有元素相等时不会进入(nums[i] > nums[j])的if语句中更新max
for (int i = 0; i < n; i++) {
dp[i] = 1;
count[i] = 1;
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) {
if (dp[i] == dp[j] + 1) {
count[i] = count[i] + count[j];
} else if (dp[i] < dp[j] + 1) {
count[i] = count[j];
dp[i] = dp[j] + 1;
}
if (dp[i] >= max) {
max = dp[i];
}
}
}
}
int res = 0;
for (int i = 0; i < n; i++) {
if (dp[i] == max) {
res += count[i];
}
}
return res;
}
}
复制代码
Lintcode 77.Longest Common Subsequence
# 02.14
# Recursive Solution
def LCS(P, Q, n, m):
if n == 0 or m == 0;
res = 0
else if P[n - 1] == Q[m - 1]:
res = 1 + LCS(P, Q, n - 1, m - 1)
else if P[n - 1] != Q[n - 1]:
res = max(LCS(P, Q, n - 1, m), LCS(P, Q, n, m - 1))
return res
复制代码
public class Solution {
public int longestCommonSubsequence(String A, String B) {
int n = A.length();
int m = B.length();
int f[][] = new int[n + 1][m + 1];
for (int i = 1; i <= n; i++) {
// 内循环也从起点开始比较, 因为是两个字符串,可以有错位情况
for (int j = 1; j <= m; j++) {
f[i][j] = Math.max(f[i - 1][j], f[i][j - 1]);
if (A.charAt(i - 1) == B.charAt(j - 1)) {
f[i][j] = f[i - 1][j - 1] + 1;
}
}
}
return f[n][m];
}
}复制代码