Day43–动态规划–674. 最长连续递增序列,300. 最长递增子序列,718. 最长重复子数组
674. 最长连续递增序列
方法:动态规划
思路:
- dp[i]含义:到i这个位置(包含i)的连续递增子序列的长度
- 递推公式:
if (nums[i] > nums[i - 1]) dp[i] = dp[i - 1] + 1;
- 初始化:每一个nums[i]的子序列都至少是1,就是自己。
Arrays.fill(dp, 1); int maxLen = 1;
- 遍历方向:正序
// 动态规划
class Solution {public int findLengthOfLCIS(int[] nums) {int n = nums.length;// dp[i]含义:到i这个位置(包含i)的连续递增子序列的长度int[] dp = new int[n];// 初始化:每一个nums[i]的子序列都至少是1,就是自己Arrays.fill(dp, 1);int maxLen = 1;// 从1开始遍历for (int i = 1; i < n; i++) {if (nums[i] > nums[i - 1]) {dp[i] = dp[i - 1] + 1;// 刷新最大值maxLen = Math.max(maxLen, dp[i]);}}return maxLen;}
}
方法:贪心
思路:
连续递增,就不断len++;不连续了,就更新最大值。
如果最长串,刚好到末尾的话,出了循环还要更新一次最大值。
class Solution {public int findLengthOfLCIS(int[] nums) {int n = nums.length;// 题目说n>1。所以至少有一个nums[i]。每个nums[i]的长度都是1int len = 1;int maxLen = 1;// 从1开始遍历(如果n==1的话,不进这个循环,答案也是对的)for (int i = 1; i < n; i++) {if (nums[i] > nums[i - 1]) {// 连续递增len++;} else {// 不连续了。刷新最大值maxLen = Math.max(maxLen, len);// 每个nums[i]的长度都是1len = 1;}}// 如果最长串,刚好到末尾的话。maxLen还没取到,所以出了循环还要再取一次return Math.max(maxLen, len);}
}
思路:
另一种写法
class Solution {public int findLengthOfLCIS(int[] nums) {int n = nums.length;// 题目说n>1。所以至少有一个nums[i]。每个nums[i]的长度都是1int len = 1;int maxLen = 1;// 从1开始遍历(如果n==1的话,不进这个循环,答案也是对的)for (int i = 1; i < n; i++) {if (nums[i] > nums[i - 1]) {// 连续递增len++;} else {// 重新开始,每个nums[i]的长度都是1len = 1;}// 如果有更大值,刷新maxLenif (len > maxLen) {maxLen = len;}}return maxLen;}
}
300. 最长递增子序列
方法:动态规划
思路:
- dp含义:dp[i]表示i之前包括i的以nums[i]结尾的最长递增子序列的长度
- 递归公式:
- 本意就是取出最大的dp[j]+1。通俗点来讲,就是接在哪个j后面,能有最大长度。
if (nums[i] > nums[j]) dp[i] = Math.max(dp[i], dp[j] + 1);
- 初始化:每一个nums[i]的子序列都至少是1,就是自己
Arrays.fill(dp, 1);
- 遍历方向:正序
// 动态规划
class Solution {public int lengthOfLIS(int[] nums) {int n = nums.length;// dp含义:dp[i]表示i之前包括i的以nums[i]结尾的最长递增子序列的长度int[] dp = new int[n];// 题目说n>=1,所以res至少也有1int res = 1;// 初始化:每一个nums[i]的子序列都至少是1,就是自己Arrays.fill(dp, 1);// 从1开始遍历for (int i = 1; i < n; i++) {// 从[0-i]中找,所以复杂度是O(n^2)for (int j = 0; j < i; j++) {if (nums[i] > nums[j]) {// 注意这里的max函数,本意不是为了比较dp[i]和dp[j]+1,而是为了取出本层最大的dp[i],因为j在遍历嘛// 本意就是取出最大的dp[j]+1。通俗点来讲,就是接在哪个j后面,能有最大长度dp[i] = Math.max(dp[i], dp[j] + 1);}}// 更新resres = Math.max(res, dp[i]);}return res;}
}
方法:贪心+二分
思路来自力扣官方题解
思路:
-
考虑一个简单的贪心,如果我们要使上升子序列尽可能的长,则我们需要让序列上升得尽可能慢,因此我们希望每次在上升子序列最后加上的那个数尽可能的小。
-
基于上面的贪心思路,我们维护一个数组 d[i] ,表示长度为 i 的最长上升子序列的末尾元素,用 len 记录目前最长上升子序列的长度,起始时 len 为 1,d[1]=nums[0]。
-
设当前已求出的最长上升子序列的长度为 len(初始时为 1),从前往后遍历数组 nums,在遍历到 nums[i] 时:
- 如果 nums[i]>d[len] ,则直接加入到 d 数组末尾,并更新 len=len+1;
- 否则,在 d 数组中二分查找,找到第一个比 nums[i] 小的数 d[k] ,并更新 d[k+1]=nums[i]。
// 贪心 + 二分(时间复杂度nlogn)
class Solution {public int lengthOfLIS(int[] nums) {int n = nums.length;if (n == 0) {return 0;}int len = 1;int[] d = new int[n + 1];d[len] = nums[0];for (int i = 1; i < n; i++) {if (nums[i] > d[len]) {d[++len] = nums[i];} else {// 调用二分查找函数寻找合适的位置int pos = binarySearch(d, 1, len, nums[i]);d[pos + 1] = nums[i];}}return len;}// 二分查找函数,单独提取出来private int binarySearch(int[] d, int left, int right, int target) {int pos = 0; // 如果找不到说明所有的数都比 target 大,此时要更新 d[1],所以这里将 pos 设为 0while (left <= right) {int mid = left - ((left - right) >> 1);if (d[mid] < target) {pos = mid;left = mid + 1;} else {right = mid - 1;}}return pos;}
}
718. 最长重复子数组
注意,这题虽然说是“子数组”,但是实际上是按“子序列”理解。
子数组是可以重新排序的,子序列是不能改变原有的顺序的(但是可以跳过一些元素)
方法:暴力
思路:
每当nums1[i] == nums2[j]
的时候,记录while(nums1[ii++] == nums2[jj++])
的长度。
但是这个方法的时间复杂度很恐怖,去到O(n^3)
class Solution {public int findLength(int[] nums1, int[] nums2) {int maxLen = 0;int n1 = nums1.length;int n2 = nums2.length;for (int i = 0; i < n1; i++) {for (int j = 0; j < n2; j++) {if (nums1[i] == nums2[j]) {int ii = i;int jj = j;while (ii < n1 && jj < n2 && nums1[ii] == nums2[jj]) {ii++;jj++;}int len = ii - i;if (len > maxLen) {maxLen = len;}}}}return maxLen;}
}
方法:动态规划(二维DP数组)
思路:
dp[i][j]
含义:以下标i - 1为结尾的A,和以下标j - 1为结尾的B,的最长重复子数组长度- 递推公式:
- 前面的一位是相等的,那么
dp[i][j]
要等于dp[i - 1][j - 1] + 1
,表明以下标i - 1为结尾的A,和以下标j - 1为结尾的B,最长重复子数组的长度增加了1 if (nums1[i - 1] == nums2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
- 前面的一位是相等的,那么
- 初始化:0
- 遍历顺序:正序
// 动态规划(二维DP数组)
class Solution {public int findLength(int[] nums1, int[] nums2) {int n1 = nums1.length;int n2 = nums2.length;// dp[i][j]含义:以下标i - 1为结尾的A,和以下标j - 1为结尾的B,的最长重复子数组长度int[][] dp = new int[n1 + 1][n2 + 1];int maxLen = 0;// 要从索引1开始遍历,到索引n才结束for (int i = 1; i <= n1; i++) {for (int j = 1; j <= n2; j++) {// 前面的一位是相等的,那么dp[i][j]等于前面的长度+1。(告诉后面到我这有多长重复)if (nums1[i - 1] == nums2[j - 1]) {dp[i][j] = dp[i - 1][j - 1] + 1;}// 刷新maxLenif (dp[i][j] > maxLen) {maxLen = dp[i][j];}}}return maxLen;}
}
方法:动态规划(一维DP数组)
思路:
《代码随想录》:
我们可以看出dp[i][j]都是由
dp[i - 1][j - 1]
推出。那么压缩为一维数组,也就是dp[j]
都是由dp[j - 1]
推出。也就是相当于可以把上一层
dp[i - 1][j]
拷贝到下一层dp[i][j]
来继续用。此时遍历B数组的时候,就要从后向前遍历,这样避免重复覆盖。
// 动态规划(一维DP数组)
class Solution {public int findLength(int[] nums1, int[] nums2) {int n1 = nums1.length;int n2 = nums2.length;// 因为把nums1的维度压缩了。所以要靠nums2作为dp// 所以要遍历nums1,nums2每层的状态要复用int[] dp = new int[n2 + 1];int maxLen = 0;for (int i = 1; i <= n1; i++) {// 因为要用到上一层的状态,所以要倒序遍历for (int j = n2; j > 0; j--) {if (nums1[i - 1] == nums2[j - 1]) {dp[j] = dp[j - 1] + 1;} else {dp[j] = 0; // 注意,不相等的话,要刷回0}// 刷新maxLenif (dp[j] > maxLen) {maxLen = dp[j];}}}return maxLen;}
}