一、最长递增子序列
题目:
给你一个整数数组 nums
,找到其中最长严格递增子序列的长度。
子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7]
是数组 [0,3,1,6,2,2,7]
的子序列。
示例 1:
输入:nums = [10,9,2,5,3,7,101,18] 输出:4 解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
示例 2:
输入:nums = [0,1,0,3,2,3] 输出:4
示例 3:
输入:nums = [7,7,7,7,7,7,7] 输出:1
思路:
首先明确dp数组的含义,本题中dp数组含义是在结尾是nums[i]的最长递增子序列的长度为dp[i],由于dp数组由nums数组相照应,因此对dp数组中的所有元素均初始化为1,启用一个判断条件,若后者元素值大于前者,则结果值加一,因此dp数组的递归关系式为
dp[i] = max(dp[j]+1,dp[i])
代码:
public int lengthOfLIS(int[] nums) {
int n = nums.length; // 获取输入数组的长度
int[] dp = new int[n]; // 创建一个数组 dp,用于记录以每个元素为结尾的最长递增子序列的长度
// 初始化 dp 数组,每个位置的初始值设为 1
// 因为每个元素至少可以组成一个长度为 1 的递增子序列(它自己)
for (int i = 0; i < n; i++) {
dp[i] = 1;
}
int result = 1; // 初始化 result,用于记录全局的最长递增子序列的长度
// 遍历每个元素,确定以每个元素为结尾的最长递增子序列的长度
for (int i = 0; i < n; i++) {
for (int j = 0; j < i; j++) {
// 如果 nums[j] 小于 nums[i],说明 nums[i] 可以接在 nums[j] 后面形成递增子序列
if (nums[j] < nums[i]) {
// 更新 dp[i] 为以 nums[i] 结尾的最长递增子序列的长度
dp[i] = Math.max(dp[j] + 1, dp[i]);
}
}
// 更新 result 为当前最大递增子序列的长度
result = result > dp[i] ? result : dp[i];
}
// 返回全局最长递增子序列的长度
return result;
}
n
是输入数组nums
的长度。dp
数组用于存储以nums[i]
为结尾的最长递增子序列的长度。初始化dp[i]
为 1,表示每个元素自身是一个长度为 1 的递增子序列。- 外层循环
i
遍历数组的每一个元素。 - 内层循环
j
遍历i
前面的所有元素,检查是否存在比nums[i]
小的元素。 - 如果
nums[j] < nums[i]
,则说明nums[i]
可以接在nums[j]
后面形成一个递增子序列,因此更新dp[i]
为dp[j] + 1
和dp[i]
的较大值。 - 更新
result
为当前的dp[i]
和result
的较大值,记录全局的最大递增子序列长度。 - 最终,
result
存储了数组中最长递增子序列的长度,返回这个值。
二、最长连续递增子序列
题目:
给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。
连续递增的子序列 可以由两个下标 l
和 r
(l < r
)确定,如果对于每个 l <= i < r
,都有 nums[i] < nums[i + 1]
,那么子序列 [nums[l], nums[l + 1], ..., nums[r - 1], nums[r]]
就是连续递增子序列。
示例 1:
输入:nums = [1,3,5,4,7] 输出:3 解释:最长连续递增序列是 [1,3,5], 长度为3。 尽管 [1,3,5,7] 也是升序的子序列, 但它不是连续的,因为 5 和 7 在原数组里被 4 隔开。
示例 2:
输入:nums = [2,2,2,2,2] 输出:1 解释:最长连续递增序列是 [2], 长度为1。
思路:
dp数组的含义是nums数组中第 i 个元素的最长连续连续子序列的产地古为dp[i],与最长连续子序列不同的是,本题子序列中的元素必须前后连续,因此需要比较的是i+1与i元素的大小,dp数组初始化仍为1,递归关系为
dp[i+1] = dp[i]+1
代码:
public int findLengthOfLCIS(int[] nums) {
int n = nums.length; // 获取输入数组的长度
int[] dp = new int[n]; // 创建一个数组 dp,用于记录以每个元素为结尾的最长连续递增子序列的长度
// 初始化 dp 数组,每个位置的初始值设为 1
// 因为每个元素至少可以组成一个长度为 1 的递增子序列(它自己)
for (int i = 0; i < dp.length; i++) {
dp[i] = 1;
}
int result = 1; // 初始化 result,用于记录全局的最长连续递增子序列的长度
// 遍历数组中的每一对相邻元素
for (int i = 0; i < n - 1; i++) {
// 如果当前元素小于下一个元素,说明可以继续形成递增子序列
if (nums[i + 1] > nums[i]) {
dp[i + 1] = dp[i] + 1; // 更新 dp[i + 1],表示以 nums[i + 1] 为结尾的递增子序列长度
}
// 更新 result 为当前的 dp[i + 1] 和 result 的较大值
result = result > dp[i + 1] ? result : dp[i + 1];
}
// 返回全局最长连续递增子序列的长度
return result;
}
n
是输入数组nums
的长度。dp
数组用于存储以每个元素为结尾的最长连续递增子序列的长度。初始化dp[i]
为 1,表示每个元素自身是一个长度为 1 的递增子序列。- 遍历数组中的每一对相邻元素,通过
i
和i + 1
访问。 - 如果
nums[i + 1] > nums[i]
,说明nums[i + 1]
可以继续延续前面的递增子序列,因此更新dp[i + 1]
为dp[i] + 1
。 - 更新
result
为当前的dp[i + 1]
和result
的较大值,以保持全局的最大连续递增子序列长度。 - 最终,
result
变量保存了数组中最长的连续递增子序列的长度,返回这个值。
三、最长重复子数组
题目:
给两个整数数组 nums1
和 nums2
,返回 两个数组中 公共的 、长度最长的子数组的长度 。
示例 1:
输入:nums1 = [1,2,3,2,1], nums2 = [3,2,1,4,7] 输出:3 解释:长度最长的公共子数组是 [3,2,1] 。
示例 2:
输入:nums1 = [0,0,0,0,0], nums2 = [0,0,0,0,0] 输出:5
思路:
有关两个数组中比较最长的公共子数组,定义dp数组以i-1为结尾的在nums1数组和以j-1为结尾nums2数组中的最长公共子数组为dp[i][j],不难推出dp数组的递推公式
dp[i][j] = dp[i-1][j-1]+1
代码:
public int findLength(int[] nums1, int[] nums2) {
// 获取两个数组的长度,并将长度加1,便于创建dp数组(包含边界情况)
int n1 = nums1.length + 1;
int n2 = nums2.length + 1;
// 初始化结果变量为0
int result = 0;
// 创建dp二维数组,dp[i][j]表示以nums1[i-1]和nums2[j-1]结尾的最长公共子数组的长度
int[][] dp = new int[n1][n2];
// 遍历dp数组填充数据
for (int i = 1; i < n1; i++) {
for (int j = 1; j < n2; j++) {
// 如果nums1的第i-1个元素等于nums2的第j-1个元素
if (nums1[i - 1] == nums2[j - 1]) {
// 更新dp[i][j],表示在dp[i-1][j-1]的基础上增加1
dp[i][j] = dp[i - 1][j - 1] + 1;
}
// 更新结果为当前dp[i][j]和已有结果的最大值
if (dp[i][j] > result) {
result = dp[i][j];
}
}
}
// 返回最长公共子数组的长度
return result;
}
n1
和n2
是nums1
和nums2
长度加 1,用于构建dp
数组(用于存储最长公共子数组的长度)。result
用于保存最终的最长公共子数组的长度。dp
数组的dp[i][j]
存储nums1[0..i-1]
和nums2[0..j-1]
中的最长公共子数组的长度。- 通过双重循环遍历
dp
数组。i
和j
分别代表nums1
和nums2
中的索引。 - 当
nums1[i - 1]
等于nums2[j - 1]
时,dp[i][j]
表示以nums1[i - 1]
和nums2[j - 1]
为结尾的最长公共子数组的长度。 - 如果
dp[i][j]
大于当前result
,则更新result
。 - 返回最长公共子数组的长度。
四、最长公共子序列
题目:
给定两个字符串 text1
和 text2
,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0
。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
- 例如,
"ace"
是"abcde"
的子序列,但"aec"
不是"abcde"
的子序列。
两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。
示例 1:
输入:text1 = "abcde", text2 = "ace" 输出:3 解释:最长公共子序列是 "ace" ,它的长度为 3 。
示例 2:
输入:text1 = "abc", text2 = "abc" 输出:3 解释:最长公共子序列是 "abc" ,它的长度为 3 。
示例 3:
输入:text1 = "abc", text2 = "def" 输出:0 解释:两个字符串没有公共子序列,返回 0 。
思路:
首先明确dp数组的含义,本题中dp数组为对于长度为[0,i-1]的text1数组和长度为[0,j-1]的text2数组,他们的最长公共子序列为dp[i][j],对于对应元素值相同的情况,dp数组的递推式为
dp[i][j] = dp[i-1][j-1] + 1
对于相对应元素不相同的情况
例如text1="abc",text2="ace"此时处理起来应该取dp[i][j-1]的长度,即为text1中跳过第二个元素取第三个,或者text2中取前两个元素舍弃第三个
同理如果是text1="ace",text2="abc",此时处理起来应该取dp[i-1][j]的长度,即为text1中取前两个元素,或者text2舍弃第三个跳过第二个元素取第三个,两者之前取最大值,因此递推式为
dp[i][j] = max(dp[i-1][j],dp[i][j-1])
代码:
public int longestCommonSubsequence(String text1, String text2) {
// 获取两个字符串的长度,并将长度加1,便于创建dp数组(包含边界情况)
int n1 = text1.length() + 1;
int n2 = text2.length() + 1;
// 将字符串转换为字符数组
char[] c1 = text1.toCharArray();
char[] c2 = text2.toCharArray();
// 创建dp二维数组,dp[i][j]表示text1前i个字符和text2前j个字符的最长公共子序列长度
int[][] dp = new int[n1][n2];
// 遍历dp数组填充数据
for (int i = 1; i < n1; i++) {
for (int j = 1; j < n2; j++) {
// 如果text1的第i-1个字符等于text2的第j-1个字符
if (c1[i - 1] == c2[j - 1]) {
// 更新dp[i][j],表示在dp[i-1][j-1]的基础上增加1
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
// 否则dp[i][j]取dp[i-1][j]和dp[i][j-1]的最大值
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
// 返回最长公共子序列的长度
return dp[text1.length()][text2.length()];
}
n1
和n2
是text1
和text2
的长度加 1,用于创建dp
数组,以包含边界条件。c1
和c2
是将text1
和text2
转换为字符数组。dp
数组的dp[i][j]
存储text1[0..i-1]
和text2[0..j-1]
的最长公共子序列的长度。- 遍历
dp
数组。i
和j
分别表示text1
和text2
中的索引。 - 当
c1[i - 1]
等于c2[j - 1]
时,dp[i][j]
表示在dp[i-1][j-1]
的基础上增加 1。 - 否则,
dp[i][j]
取dp[i-1][j]
和dp[i][j-1]
的最大值,表示不匹配时最长公共子序列的长度。 - 返回
dp
数组中最后一个元素,即text1
和text2
的最长公共子序列的长度。
今天的学习就到这里