力扣题目链接(opens new window)
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
示例 1:
示例 2:
示例 3:
提示:
思路:动态规划 题目要求最长递增子序列。子序列这一类型题目,可以使用动态规划作为通解 看题目感觉很简单,找最长递增子序列而已。但越想越难,甚至暴力算法也没有思路 1.dp数组以及下标代表含义 初始想法是定义dp一维数组,i表示当前下标以及之前数组元素之中,最长递增子序列的长度 但这种想法有缺陷。定义dp数组是为递推公式服务,需要找到dp[i] 和 dp[i-2] 甚至有可能是dp[i-3] dp[i-4]之间的关系,然后推导递推公式 假如说nums[i] > nums[i-1] 那么dp[i] 就一定比 dp[i-1]大吗?其实不是的 因为对dp数组的定义为【当前下标以及之前数组元素之中,最长递增子序列的长度】。最长子序列不一定会包含nums[i-1],nums[i] 那么这两个元素的比较没有意义。 怎样才能让dp[i-1] 和 dp[i]的比较有意义? 修改dp数组的定义方式:定义dp一维数组,i表示以nums[i]为结尾的最长递增子序列的长度 2.确定递推公式 以i为起点,向前遍历。若 nums[j] > nums[i] dp[i] = Math.max(dp[j] + 1,dp[i]) 3.dp数组初始化 dp数组初始值为1 4.遍历顺序,dp[i]由dp[i-1]推导,故正序 5.举例推导dp数组 时间复杂度o(n) 空间复杂度o(n)
代码如下
public static void main(String[] args) {
int[] prices = new int[]{0, 3, 1, 6, 2, 2, 7};
lengthOfLIS(prices);
}
public static int lengthOfLIS(int[] nums) {
if (nums == null || nums.length == 0)
return 0;
int[] dp = new int[nums.length];
for (int i = 0; i < nums.length; i++)
dp[i] = 1;
int result = 1;
for (int i = 1; i < nums.length; i++) {
for (int j = i - 1; j >= 0; j--) {
if (nums[j] < nums[i]) {
dp[i] = Math.max(dp[j] + 1, dp[i]);
}
if (dp[i] > result)
result = dp[i];
}
}
return result;
}
二刷时,只能想出dp数组的定义,但对于递推公式的推导还是没有思路
我一直在想如何把dp[i] 和dp[i-1]关联起来,结果关联不上
因为本题要求非连续递增子序列,不能只比较nums[i]与nums[i - 1],而需要比较nums[j]与nums[i] (j是在0到i之间遍历)。
力扣题目链接(opens new window)
给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。
连续递增的子序列 可以由两个下标 l 和 r(l < r)确定,如果对于每个 l <= i < r,都有 nums[i] < nums[i + 1] ,那么子序列 [nums[l], nums[l + 1], …, nums[r - 1], nums[r]] 就是连续递增子序列。
示例 1:
示例 2:
提示:
思路:动态规划
题目不难,也可以使用贪心算法解决,甚至空间复杂度更低,为o(1)
但重点要理解此题和【最长上升子序列】的区别
一个是求最长连续子序列,一个是求最长不连续子序列
最长连续子序列:只需要当前状态和前一个状态对比
最长不连续子序列:需要当前状态i和前 i-1个状态对比
时间复杂度:O(n)
空间复杂度:O(n)
代码如下
public int findLengthOfLCIS(int[] nums) {
if (nums == null || nums.length == 0)
return 0;
int[] dp = new int[nums.length];
for (int i = 0; i < nums.length; i++)
dp[i] = 1;
int result = 1;
for (int i = 1; i < nums.length; i++) {
if(nums[i] > nums[i-1])
dp[i] = dp[i-1] + 1;
if(result < dp[i])
result = dp[i];
}
return result;
}
力扣题目链接(opens new window)
给两个整数数组 A 和 B ,返回两个数组中公共的、长度最长的子数组的长度。
示例:
输入:
提示:
思路:动态规划 动态规划五部曲 1.dp数组含义及其下标 定义二维数组dp[i][j]。 当AB数组元素为nums[i],nums[j]时,公共子数组(结尾为nums[i] 和 nums[j])的最长长度。 2.定义递推公式 if nums[i] == nums[j] dp[i][j] = dp[i-1][j-1] + 1 3.dp数组初始化 dp[0][0] = 0 4.遍历顺序 从前向后遍历 5.举例推导dp数组 // 时间复杂度o(n^2) // 空间复杂度o(n)
代码如下
public static void main(String srgs[]) {
int[] nums1 = new int[]{0, 1, 1, 0, 1, 1, 1, 0, 1, 0};
int[] nums2 = new int[]{1, 0, 0, 0, 1, 0, 0, 1, 1, 0};
findLength(nums1, nums2);
}
public static int findLength(int[] nums1, int[] nums2) {
if (nums1 == null || nums2 == null)
return 0;
int[][] dp = new int[nums1.length][nums2.length];
int result = 0;
for (int i = 0; i < nums1.length; i++) {
if (nums1[i] == nums2[0]) {
dp[i][0] = 1;
result = 1;
}
}
for (int j = 0; j < nums2.length; j++) {
if (nums2[j] == nums1[0]) {
dp[0][j] = 1;
result = 1;
}
}
for (int i = 1; i < nums1.length; i++) {
for (int j = 1; j < nums2.length; j++) {
if (nums1[i] == nums2[j]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
result = Math.max(result, dp[i][j]);
}
}
}
return result;
}
优化 二刷时采用另一种dp数组的定义方式。 这种方式可简化初始化dp数组 个人理解这种dp数组的定义方式适用于【编辑距离】一类的题目 【编辑距离】这类题目特点:从两个数组或者字符串中,使用增删改等步骤,找出符合某些特征的结果 注意题目中说的子数组,其实就是连续子序列。我忽略了这一点,以为求非连续子序列,导致递推公式增加nums1[i - 1] != nums2[j - 1]的情况 dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
代码如下
public static void main(String srgs[]) {
int[] nums1 = new int[]{0,1,1,1,1};
int[] nums2 = new int[]{1,0,1,0,1};
findLength(nums1, nums2);
}
public static int findLength(int[] nums1, int[] nums2) {
if (nums1 == null || nums2 == null)
return 0;
int[][] dp = new int[nums1.length + 1][nums2.length + 1];
int result = 0;
for (int i = 1; i <= nums1.length; i++) {
for (int j = 1; j <= nums2.length; j++) {
if (nums1[i - 1] == nums2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
}
result = Math.max(result, dp[i][j]);
}
}
return result;
}