算法 ---- 子序列系列问题题解(子序列编辑距离回文系列问题)
Posted TheWhc
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法 ---- 子序列系列问题题解(子序列编辑距离回文系列问题)相关的知识,希望对你有一定的参考价值。
子序列题解问题
子序列(不连续)
300. 最长递增子序列
/**
* 思路: 动态规划
* 1. 确定dp数组以及下标含义
* dp[i]: 表示包含下标i在内的最长递增子序列
* 2. 确定递推公式
* 位置i的最长上升子序列等于j从0到i-1各个位置的最长上升子序列+1的最大值
* if(nums[i] > nums[j])
* dp[i] = Math.max(dp[i], dp[j]+1)
* 3. 初始化
* 每一个dp[i]至少都是1
* 4. 确定遍历顺序
* i是从前往后遍历
* j是从0开始到i-1遍历
* 5. 举例推导dp数组
* [0, 1, 0, 3, 2]
* i=1 1 2 1 1 1
* i=2 1 2 1 1 1
* i=3 1 2 1 3 1
* i=4 1 2 1 3 3
*
* 时间: O(n^2)
* 空间: O(n)
*/
public int lengthOfLIS(int[] nums) {
int[] dp = new int[nums.length];
Arrays.fill(dp, 1);
int res = 1;
for (int i = 1; i < nums.length; i++) {
for (int j = 0; j < i; j++) {
if(nums[i] > nums[j])
dp[i] = Math.max(dp[i], dp[j]+1);
}
// 取长的子序列
res = Math.max(res, dp[i]);
}
return res;
}
/**
* 思路: 贪心
*
* 二分插入法
*
* 1. 对原序列进行遍历,将每位元素二分插入dp数组中
* - 如果dp数组中元素都比它小,将它插到最后
* - 否则,用它覆盖掉比它大的元素中最小的那个
*
* 2. dp数组未必是真实的最长上升子序列,但长度是对的
*
* 时间: O(nlogn)
* 空间: O(n)
*/
public int lengthOfLIS(int[] nums) {
// dp数组是递增的
int[] dp = new int[nums.length];
dp[0] = nums[0];
// dp数组最后一个元素的下标,即dp中最大元素的下标
int end = 0;
for (int i = 1; i < nums.length; i++) {
int num = nums[i];
// 当前元素大于dp数组最后一个元素,则直接插入到后面
if(num > dp[end]) {
dp[++end] = num;
continue;
}
// 二分找到比num元素大的元素中最小的那个
int left = 0;
int right = end;
while(left < right) {
int mid = left + (right - left) / 2;
if(dp[mid] < num) {
left = mid + 1;
} else {
// 注意这里的right = mid
right = mid;
}
}
dp[left] = num;
}
return end+1;
}
1143. 最长公共子序列
/**
* 思路: 动态规划
*
* 1. 确定dp数组以及下标含义
* dp[i][j]表示s1包含下标i在内的子串和s2包含下标j在内的子串的最长公共子序列
* 2. 确定递推公式
* if(s1[i] == s2[j]) {
* dp[i][j] = dp[i-1][j-1] + 1
* } else {
* dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1])
* }
*
* 3. 初始化
* i=0时, dp[0][j] = 0
* j=0时, dp[i][0] = 0
* 4. 遍历顺序
* 外层遍历s1,内层遍历s2
*
* 时间: O(n * m)
* 空间: O(n * m)
*/
/*public int longestCommonSubsequence(String text1, String text2) {
int m = text1.length();
int n = text2.length();
int[][] dp = new int[m+1][n+1];
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if(text1.charAt(i-1) == text2.charAt(j-1)) {
dp[i][j] = dp[i-1][j-1] + 1;
} else {
dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]);
}
}
}
return dp[m][n];
}*/
// 动态规划 + 空间优化
// 时间: O(n * m)
// 空间: O(n)
public int longestCommonSubsequence(String text1, String text2) {
int m = text1.length();
int n = text2.length();
int[] dp = new int[n+1];
int leftUp = 0;
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
int temp = dp[j];
if(text1.charAt(i-1) == text2.charAt(j-1)) {
dp[j] = leftUp + 1;
} else {
dp[j] = Math.max(dp[j], dp[j-1]);
}
leftUp = temp;
}
leftUp = 0;
}
return dp[n];
}
1035. 不相交的线
/**
* 思路: 动态规划
* 1. 确定dp数组以及下标含义
* dp[i][j]表示s1中包含下标i在内的元素和s2中包含下标j在内的元素的最多相同元素
* 2. 确定递推公式
* if(nums[i] == nums[j]) {
* dp[i][j] = dp[i-1][j-1] + 1
* } else {
* dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1])
* }
* 3. 初始化
* i=0时,dp[0][j] = 0
* j=0时,dp[i][0] = 0
* 4. 遍历顺序
* 外层先遍历nums1,内层遍历nums2
*
* 时间: O(n*m)
* 空间: O(n*m)
*
*/
/*public int maxUncrossedLines(int[] nums1, int[] nums2) {
int n = nums1.length;
int m = nums2.length;
int[][] dp = new int[n+1][m+1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if(nums1[i-1] == nums2[j-1]) {
dp[i][j] = dp[i-1][j-1] + 1;
} else {
dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]);
}
}
}
return dp[n][m];
}*/
// 动态规划 + 空间优化
// 时间: O(n*m)
// 空间: O(m)
public int maxUncrossedLines(int[] nums1, int[] nums2) {
int n = nums1.length;
int m = nums2.length;
int[] dp = new int[m+1];
int leftUp = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
int temp = dp[j];
if(nums1[i-1] == nums2[j-1]) {
dp[j] = leftUp + 1;
} else {
dp[j] = Math.max(dp[j], dp[j-1]);
}
leftUp = temp;
}
leftUp = 0;
}
return dp[m];
}
子序列(连续)
674. 最长连续递增序列
贪心
/**
* 思路: 贪心
* 1. 遍历数组,统计连续增长的子序列长度
* 2. 一旦遇到不连续的元素,则重新开始统计
*
* 时间: O(n)
* 空间: O(1)
*/
public int findLengthOfLCIS(int[] nums) {
int res = 1;
int lenOfChild = 1;
for (int i = 1; i < nums.length; i++) {
if(nums[i] > nums[i-1]) {
lenOfChild++;
} else {
// 重新开始统计
lenOfChild = 1;
}
res = Math.max(res, lenOfChild);
}
return res;
}
动态规划
/**
* 思路: 动态规划
* 1. 确定dp数组以及下标含义
* dp[i]表示包含下标i在内的最长连续递增序列
* 2. 确定递推公式
* if(nums[i] == nums[i-1]) {
* dp[i] = dp[i-1] + 1;
* } else {
* dp[i] = 1;
* }
* 3. 初始化
* dp[0] = 1
* 4. 遍历顺序
* 从前往后遍历
*
* 时间: O(n)
* 空间: O(n)
*/
/*public int findLengthOfLCIS(int[] nums) {
int[] dp = new int[nums.length];
dp[0] = 1;
int res = 1;
for (int i = 1; i < nums.length; i++) {
if(nums[i] > nums[i-1]) {
dp[i] = dp[i-1] + 1;
} else {
dp[i] = 1;
}
res = Math.max(res, dp[i]);
}
return res;
}*/
// 动态规划 + 空间优化
// 时间: O(n)
// 空间: O(1)
public int findLengthOfLCIS(int[] nums) {
int dp = 1;
int res = 1;
for (int i = 1; i < nums.length; i++) {
if(nums[i] > nums[i-1]) {
dp = dp + 1;
} else {
dp = 1;
}
res = Math.max(res, dp);
}
return res;
}
718. 最长重复子数组
/**
* 思路: 动态规划
* 1. dp[i][j] : 表示nums1中包含下标i在内的数组元素和nums2中包含下标j在内的数组元素最长重复子数组(连续的)
* 2. if(nums1[i-1] == nums2[j-1]) {
* dp[i][j] = dp[i-1][j-1] + 1;
* }
* 3. dp[0][j] = 0, dp[i][0] = 0
* 4. 外层遍历nums1,内层遍历nums2
*
* 时间: O(n*m)
* 空间: O(n*m)
*/
/*public int findLength(int[] nums1, int[] nums2) {
int n = nums1.length;
int m = nums2.length;
int[][] dp = new int[n+1][m+1];
int res = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if(nums1[i-1] == nums2[j-1]) {
dp[i][j] = dp[i-1][j-1] + 1;
}
res = Math.max(res, dp[i][j]);
}
}
return res;
}*/
// 动态规划 + 空间优化
// 时间: O(n*m) 空间: O(m)
// 注意: nums2应该从后往前遍历, 如果从前往后遍历, 会出现覆盖问题
public int findLength(int[] nums1, int[] nums2) {
int n = nums1.length;
int m = nums2.length;
int[] dp = new int[m+1];
int res = 0;
for (int i = 1; i <= n; i++) {
for (int j = m; j > 0; j--) {
if(nums1[i-1] == nums2[j-1]) {
dp[j] = dp[j-1] + 1;
} else {
// 注意这里不相等要等于0
dp[j] = 0;
}
res = Math.max(res, dp[j]);
}
}
return res;
}
53. 最大子序和
贪心
/**
* 思路: 贪心
* 遍历数组,如果出现sumOfChild < 0, 则开始重新计算连续子序和
* 注意sumOfChild要写在第一步进行判断,这样当出现[-2,-1,-4]时, 每次重新计算连续子序和, 都能把当前元素纳入计算范围
*
* 时间: O(n)
* 空间: O(1)
*/
public int maxSubArray(int[] nums) {
int sumOfChild = nums[0];
int res = nums[0];
for (int i = 1; i < nums.length; i++) {
if(sumOfChild < 0) {
sumOfChild = 0;
}
sumOfChild += nums[i];
res = Math.max(res, sumOfChild);
}
return res;
}
动态规划
/**
* 思路: 动态规划
* 1. dp[i]表示包含下标i在内的最大子序和
* 2. if(dp[i-1] < 0) {
* dp[i] = nums[i];
* } else {
* dp[i] = dp[i-1] + nums[i];
* }
* 3. 初始化
* dp[0] = nums[0]
* 4. 遍历顺序
* 从前往后遍历
*
* 时间: O(n)
* 空间: O(n)
*/
/*public int maxSubArray(int[] nums) {
int[] dp = new int[nums.length];
dp[0] = nums[0];
int res = nums[0];
for (int i = 1; i < nums.length; i++) {
if(dp[i-1] < 0) {
dp[i] = nums[i];
} else {
dp[i] = dp[i-1] + nums[i];
}
res = Math.max(res, dp[i]);
}
return res;
}*/
// 动态规划 + 空间优化
// 时间: O(n)
// 空间: O(1)
public int maxSubArray(int[] nums) {
int dp = nums[0];
int res = nums[0];
for (int i = 1; i < nums.length; i++) {
if(dp < 0) {
dp = nums[i];
} else {
dp = dp + nums[i];
}
res = Math.max(res, dp);
}
return res;
}
编辑距离
392. 判断子序列
双指针法
/**
* 思路: 双指针法
* 1. 指针i指向s, 指向j指向t
* 2. 不断移动指针j,一直遍历到结束,除非i指针已到达s末尾
* 3. 若指针i和指针j指向的字符匹配时,则i++
*
* 时间: O(n+m)
* 空间: O(1)
*/
public boolean isSubsequence(String s, String t) {
int n = s.length();
int m = t.length();
int i = 0;
int j = 0;
while(i < n && j < m) {
if(s.charAt(i) == t.charAt(j)) {
i++;
}
if(i == s.length()) {
break;
}
j++;
}
return i == n;
}
动态规划
/**
* 思路: 动态规划
* 1. dp[i][j]: 表示s包含下标i在内的子串和t包含下标j在内的子串的相同子序列长度
* 2. if(s[i] == t[j]) {
* dp[i][j] = dp[i-1][j-1] + 1;
* } else {
* // 不匹配时,相当于t要删除元素,继续匹配
* dp[i][j] = dp[i][j-1];
* }
* 3. dp[0][j] = 0, dp[i][0] = 0
* 4. 外层遍历s,内层遍历t
*
* 时间: O(n * m)
* 空间: O(n * m)
*/
public boolean isSubsequence(String s, String t) {
int n = s.length();
int m = t.length();
int[][] dp = new int[n+1][m+1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if(s.charAt(i-1) == t.charAt(j-1)) {
dp[i][j] = dp[i-1][j-1] + 1;
} else {
dp[i][j] = dp[i][j-1];
}
}
}
return dp[n][m] == n;
}
115. 不同的子序列
/**
* 思路: 动态规划
* 1. 确定dp数组以及下标含义
* dp[i][j] : 以i-1为结尾的s子序列中出现以j-1为结尾的t的个数为dp[i][j]
* 2. 确定递推公式
* if(s[i] == t[j]) {
* dp[i][j] = dp[i-1][j-1] + dp[i-1][j];
* // dp[i-1][j-1]表示匹配s[i]和t[j], dp[i-1][j]表示不匹配s[i]和t[j]
* // 比如baegg 和 bag, 求dp[5][3]时, 匹配最后一个字符,s可以组成bag, 不匹配最后一个字符,s也可以组成bag
* } else {
* dp[i][j] = dp[i-1][j];
* }
*
* 3. 确定遍历顺序
* 外层遍历s,内层遍历t
*
* 4. 初始化
* i=0时, 除了dp[0][0]外, dp[0][j]一定都为0, 因为空字符串s无论如何都无法组成t
* j=0时, dp[i][0]一定都为1, 因为字符串s可以删除全部字符,组成空字符串t
*
* 时间: O(n*m)
* 空间: O(n*m)
*/
/*public int numDistinct(String s, String t) {
int n = s.length();
int m = t.length();
int[][] dp = new int[n+1][m+1];
for (int i = 0; i < n; i++) {
dp[i][0] = 1;
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if(s.charAt(i-1) == t.charAt(j-1)) {
dp[i][j] = dp[i-1][j-1] + dp[i-1][j];
} else {
dp[i][j] = dp[i-1][j];
}
}
}
return dp[n][m];
}*/
// 动态规划 + 空间压缩
// 时间: O(n * m)
// 空间: O(m)
public int numDistinct(String s, String t) {
int n = s.length();
int m = t.length();
int[] dp = new int[m+1];
dp[0] = 1;
int leftUp = dp[0];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
int temp = dp[j];
if(s.charAt(i-1) == t.charAt(j-1)) {
dp[j] = leftUp + dp[j];
}
leftUp = temp;
}
leftUp = dp[0];
}
return dp[m];
}
583. 两个字符串的删除操作
/**
* 思路: 动态规划
*
* 1. 确定dp数组以及下标含义
* dp[i][j]:表示word1[i]和word2[j]达到相等,所需要删除元素的最少次数
* 2. 确定递推公式
* if(word1[i] == word2[j]) {
* dp[i][j] = dp[i-1][j-1];
* } else {
* // 分三种情况
* // ① 删word1[i],最少操作数 = dp[i-1][j] + 1
* // ② 删word2[j],最少操作数 = dp[i][j-1] + 1
* // ③ 同时删word1[i]和word2[j],最少操作数 = dp[i-1][j-1] + 2
* dp[i][j] = Math.min(Math.min(dp[i-1][j]+1, dp[i][j-1]+1),dp[i-1][j-1]+2);
* }
* 3. 初始化
* dp[0][j] = j;
* dp[i][0] = i;
* 4. 遍历顺序
* 先遍历word1,后遍历word2
*
* 5. 举例推导dp数组
* 比如"sea"和"eat"
* e a t
* 0 1 2 3
* s 1 2 3 4
* e 2 1 2 3
* a 3 2 1 2
*
* 时间: O(n*m)
* 空间: O(n*m)
*/
/*public int minDistance(String word1, String word2) {
int n = word1.length();
int m = word2.length();
int[][] dp = new int[n+1][m+1];
for (int i = 0; i <= n; i++) {
dp[i][0] = i;
}
for (int j = 0; j <= m; j++) {
dp[0][j] = j;
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if(word1.charAt(i-1) == word2.charAt(j-1)) {
dp[i][j] = dp[i-1][j-1];
} else {
dp[i][j] = Math.min(Math.min(dp[i-1][j]+1, dp[i][j-1]+1),dp[i-1][j-1]+2);
}
}
}
return dp[n][m];
}*/
// 动态规划 + 空间优化
// 时间: O(n*m)
// 空间: O(m)
public int minDistance(String word1, String word2) {
int n = word1.length();
int m = word2.length();
int[] dp = new int[m+1];
for (int i = 0; i <= m; i++) {
dp[i] = i;
}
int leftUp = dp[0];
for (int i = 1; i <= 以上是关于算法 ---- 子序列系列问题题解(子序列编辑距离回文系列问题)的主要内容,如果未能解决你的问题,请参考以下文章
算法 ---- 子序列系列问题题解(子序列编辑距离回文系列问题)
动态规划线性dp问题总结:数字三角形最长上升子序列最长公共子序列最短编辑距离 题解与模板