300. 最长递增子序
题目:300. 最长递增子序列 - 力扣(LeetCode)
题解:代码随想录
状态:半AC
思路
- dp[i]表示i之前包括i的以nums[i]结尾的最长递增子序列的长度
- if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1)
- 初始化:i对应的dp[i]起始大小至少都是1
代码
时间复杂度:O(N^2) 空间复杂度:O(N)
class Solution {
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;
}
}
674. 最长连续递增序列
题目:674. 最长连续递增序列 - 力扣(LeetCode)
题解:代码随想录
状态:AC
思路
- dp[i]:以下标i为结尾的连续递增的子序列长度为dp[i]
- if (nums[i] > nums[i - 1]) dp[i] = dp[i - 1] + 1;
- dp[i]应该初始为1
代码
时间复杂度:O(N) 空间复杂度:O(N)
class Solution {
public int findLengthOfLCIS(int[] nums) {
int[] dp = new int[nums.length];
Arrays.fill(dp, 1);
int res = 1;
for(int i = 1; i < nums.length; i++){
if(nums[i] > nums[i - 1]){
dp[i] = dp[i - 1] + 1;
res = Math.max(res, dp[i]);
}
}
return res;
}
}
718. 最长重复子数组
题目:718. 最长重复子数组 - 力扣(LeetCode)
题解:代码随想录
状态:半AC,递推公式不明确
思路
特别注意:dp[i][j]是以下标i - 1为结尾的A,和以下标j - 1为结尾的B,最长重复子数组长度为dp[i][j]
递推公式:当nums1[i - 1] == nums[j - 1]时,dp[i][j] = dp[i - 1][j - 1] + 1
代码
时间复杂度:O(N * M) 空间复杂度:O(N * M)
class Solution {
public int findLength(int[] nums1, int[] nums2) {
int n1 = nums1.length, n2 = nums2.length;
int[][] dp = new int[n1 + 1][n2 + 1];
int res = 0;
for (int i = 1; i <= n1; i++) {
for (int j = 1; j <= n2; 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;
}
}
1143.最长公共子序列
题目:1143. 最长公共子序列 - 力扣(LeetCode)
题解:代码随想录
状态:AC
思路
递推公式加一行:
if(nums1[i - 1] == nums2[j - 1]){
dp[i][j] = dp[i - 1][j - 1] + 1;
res = Math.max(res, dp[i][j]);
}else{
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
代码
时间复杂度:O(N * M) 空间复杂度:O(N * M)
class Solution {
public int maxUncrossedLines(int[] nums1, int[] nums2) {
int len1 = nums1.length, len2 = nums2.length;
int[][] dp = new int[len1 + 1][len2 + 1];
int res = 0;
for(int i = 1; i <= len1; i++){
for(int j = 1; j <= len2; j++){
if(nums1[i - 1] == nums2[j - 1]){
dp[i][j] = dp[i - 1][j - 1] + 1;
res = Math.max(res, dp[i][j]);
}else{
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return res;
}
}
1035.不相交的线
题解:代码随想录
状态:AC
思路
代码和上题一样,但要学会思路的转变:
-
直线不能相交,说明在nums1中 找到一个与nums2相同的子序列,且这个子序列不能改变相对顺序,只要相对顺序不改变,连接相同数字的直线就不会相交。
-
本题说是求绘制的最大连线数,其实就是求两个字符串的最长公共子序列的长度
代码
时间复杂度:O(N * M) 空间复杂度:O(N * M)
class Solution {
public int maxUncrossedLines(int[] nums1, int[] nums2) {
int len1 = nums1.length, len2 = nums2.length;
int[][] dp = new int[len1 + 1][len2 + 1];
int res = 0;
for(int i = 1; i <= len1; i++){
for(int j = 1; j <= len2; j++){
if(nums1[i - 1] == nums2[j - 1]){
dp[i][j] = dp[i - 1][j - 1] + 1;
res = Math.max(res, dp[i][j]);
}else{
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return res;
}
}
53. 最大子序和
题解:代码随想录
状态:需要多复习
思路
dp[i]:包括下标i(以nums[i]为结尾)的最大连续子序列和为dp[i]
dp[i]只有两个方向可以推出来:
- dp[i - 1] + nums[i],即:nums[i]加入当前连续子序列和
- nums[i],即:从头开始计算当前连续子序列和
一定是取最大的,所以dp[i] = max(dp[i - 1] + nums[i], nums[i]);
代码
时间复杂度:O(N) 空间复杂度:O(N)
class Solution {
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++){
dp[i] = Math.max(dp[i - 1] + nums[i], nums[i]);
res = Math.max(res, dp[i]);
}
return res;
}
}
392.判断子序列
题解:代码随想录
状态:AC
思路
在最长公共子序列的基础上,加个判断即可,但注意递推公式可做简化
代码
时间复杂度:O(N * M) 空间复杂度:O(N * M)
class Solution {
public boolean isSubsequence(String s, String t) {
int sLen = s.length(), tLen = t.length();
if(sLen == 0) return true;
int[][] dp = new int[sLen + 1][tLen + 1];
for(int i = 1; i <= sLen; i++){
for(int j = 1; j <= tLen; 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[sLen][tLen] == sLen;
}
}
115.不同的子序列
题解:代码随想录
状态:需要多复习
思路
-
dp[i][j]:以i-1为结尾的s子序列中出现以j-1为结尾的t的个数为dp[i][j]
-
当s[i - 1] 与 t[j - 1]相等时,dp[i][j]可以有两部分组成:
- 一部分是用s[i - 1]来匹配,那么个数为dp[i - 1][j - 1]。即不需要考虑当前s子串和t子串的最后一位字母,所以只需要 dp[i-1][j-1]
- 一部分是不用s[i - 1]来匹配,个数为dp[i - 1][j]。
-
当s[i - 1] 与 t[j - 1]不相等时,dp[i][j]只有一部分组成,不用s[i - 1]来匹配(就是模拟在s中删除这个元素),即:dp[i - 1][j]
-
dp[i][0] 表示:以i-1为结尾的s可以随便删除元素,出现空字符串的个数。那么dp[i][0]一定都是1,因为也就是把以i-1为结尾的s,删除所有元素,出现空字符串的个数就是1。
代码
时间复杂度:O(N * M) 空间复杂度:O(N * M)
class Solution {
public boolean isSubsequence(String s, String t) {
int sLen = s.length(), tLen = t.length();
if(sLen == 0) return true;
int[][] dp = new int[sLen + 1][tLen + 1];
for(int i = 1; i <= sLen; i++){
for(int j = 1; j <= tLen; 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[sLen][tLen] == sLen;
}
}
583. 两个字符串的删除操作
题目:583. 两个字符串的删除操作 - 力扣(LeetCode)
题解:代码随想录
状态:
思路
思路1:将题目转换为求最长公共子序列的变体
思路2:将题目转换为不同子序列的变体
- dp[i][j]:以i-1为结尾的字符串word1,和以j-1位结尾的字符串word2,想要达到相等,所需要删除元素的最少次数
- 当word1[i - 1] 与 word2[j - 1]相同的时候,dp[i][j] = dp[i - 1][j - 1];
- 当word1[i - 1] 与 word2[j - 1]不相同的时候,有三种情况:
-
情况一:删word1[i - 1],最少操作次数为dp[i - 1][j] + 1
-
情况二:删word2[j - 1],最少操作次数为dp[i][j - 1] + 1
-
情况三:同时删word1[i - 1]和word2[j - 1],操作的最少次数为dp[i - 1][j - 1] + 2
-
最后当然是取最小值,所以当word1[i - 1] 与 word2[j - 1]不相同的时候,递推公式:dp[i][j] = min({dp[i - 1][j - 1] + 2, dp[i - 1][j] + 1, dp[i][j - 1] + 1});
-
因为 dp[i][j - 1] + 1 = dp[i - 1][j - 1] + 2,所以递推公式可简化为:dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1);
-
这里可能不少录友有点迷糊,从字面上理解就是当同时删word1[i - 1]和word2[j - 1],dp[i][j-1] 本来就不考虑 word2[j - 1]了,那么我在删 word1[i - 1],是不是就达到两个元素都删除的效果,即 dp[i][j-1] + 1。
- dp[i][0]:word2为空字符串,以i-1为结尾的字符串word1要删除多少个元素,才能和word2相同呢,很明显dp[i][0] = i。dp[0][j]的话同理
代码
时间复杂度:O(N * M) 空间复杂度:O(N * M)
(最长公共子序列变体)
class Solution {
public int minDistance(String word1, String word2) {
int len1 = word1.length(), len2 = word2.length();
int res = 0;
int[][] dp = new int[len1 + 1][len2 + 1];
for(int i = 1; i <= len1; i++){
for(int j = 1; j <= len2; j++){
if(word1.charAt(i - 1) == word2.charAt(j - 1)){
dp[i][j] = dp[i - 1][j - 1] + 1;
res = Math.max(dp[i][j], res);
}else{
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return len1 + len2 - res * 2;
}
}
(不同的子序列变体)
class Solution {
public int minDistance(String word1, String word2) {
int len1 = word1.length(), len2 = word2.length();
int[][] dp = new int[len1 + 1][len2 + 1];
for(int i = 0; i <= len1; i++) dp[i][0] = i;
for(int j = 0; j <= len2; j++) dp[0][j] = j;
for(int i = 1; i <= len1; i++){
for(int j = 1; j <= len2; j++){
if(word1.charAt(i - 1) == word2.charAt(j - 1)){
dp[i][j] = dp[i - 1][j - 1];
}else{
dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1);
}
}
}
return dp[len1][len2];
}
}
72. 编辑距离
题解:代码随想录
状态:需要结合上面几题多复习
思路
- dp[i][j] 表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2,最近编辑距离为dp[i][j]
if (word1[i - 1] == word2[j - 1])
不操作:dp[i][j] = dp[i - 1][j - 1]
if (word1[i - 1] != word2[j - 1])
增删(其实可以都转换为增或删):dp[i][j] = dp[i - 1][j] + 1、dp[i][j] = dp[i][j - 1] + 1
换:dp[i][j] = dp[i - 1][j - 1] + 1
代码
时间复杂度:O(N * M) 空间复杂度:O(N * M)
class Solution {
public int minDistance(String word1, String word2) {
int len1 = word1.length(), len2 = word2.length();
int[][] dp = new int[len1 + 1][len2 + 1];
for(int i = 0; i <= len1; i++) dp[i][0] = i;
for(int j = 0; j <= len2; j++) dp[0][j] = j;
for (int i = 1; i <= len1; i++) {
for (int j = 1; j <= len2; j++) {
if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = Math.min(dp[i - 1][j] + 1, Math.min(dp[i][j - 1] + 1, dp[i - 1][j - 1] + 1));
}
}
}
return dp[len1][len2];
}
}
647. 回文子串
题解:代码随想录
状态:需要多复习
思路
- 布尔类型的dp[i][j]:表示区间范围[i,j] (注意是左闭右闭)的子串是否是回文子串
- result统计回文子串的数量
- 当s[i]与s[j]不相等,dp[i][j]一定是false
- 当s[i]与s[j]相等时,这就复杂一些了,有如下三种情况
- 情况一:下标i与j相同,同一个字符例如a,当然是回文子串,res++
- 情况二:下标i与j相差为1,例如aa,也是回文子串,res++
- 情况三:下标i与j相差大于1的时候,就取决于dp[i + 1][j - 1],为true则res++
- dp初始化一定全为false,因为一开始都是不匹配的
- 要从下到上,从左到右遍历,这样保证dp[i + 1][j - 1]都是经过计算的
代码
时间复杂度:O(N^2) 空间复杂度:O(N^2)
class Solution {
public int countSubstrings(String s) {
int len = s.length();
boolean[][] dp = new boolean[len][len];
int res = 0;
for (int i = len - 1; i >= 0; i--) {
for (int j = i; j < len; j++) {
if (s.charAt(i) == s.charAt(j)) {
if (j - i < 2) {
dp[i][j] = true;
res++;
} else if (dp[i + 1][j - 1] == true) {
dp[i][j] = true;
res++;
}
}
}
}
return res;
}
}
516.最长回文子序列
题目:516. 最长回文子序列 - 力扣(LeetCode)
题解:代码随想录
状态:需要多复习
思路
- dp[i][j]:字符串s在[i, j]范围内最长的回文子序列的长度为dp[i][j]
- 如果s[i]与s[j]相同,那么dp[i][j] = dp[i + 1][j - 1] + 2
- 如果s[i]与s[j]不相同,说明s[i]和s[j]的同时加入并不能增加[i,j]区间回文子序列的长度,那么分别加入s[i]、s[j]看看哪一个可以组成最长的回文子序列:dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]);
- 遍历i的时候一定要从下到上遍历,这样才能保证下一行的数据是经过计算的
代码
时间复杂度:O(N^2) 空间复杂度:O(N^2)
class Solution {
public int longestPalindromeSubseq(String s) {
int len = s.length();
int[][] dp = new int[len + 1][len + 1];
for (int i = len - 1; i >= 0; i--) { // 从后往前遍历 保证情况不漏
dp[i][i] = 1; // 初始化
for (int j = i + 1; j < len; j++) {
if (s.charAt(i) == s.charAt(j)) {
dp[i][j] = dp[i + 1][j - 1] + 2;
} else {
dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]);
}
}
}
return dp[0][len - 1];
}
}