文章目录
计算最长系列(动态规划)
594. 最长和谐子序列
1.题目描述
leetcode链接:594. 最长和谐子序列
2.思路分析
方法一:排序+双指针
首先,堆数组进行从小到大排序,然后一个指针一次遍历,另一个指针判断之间的差值是否大于1。最大的长度就是两个指针之间的距离。
方法二:哈希计数
统计相邻两个数出现的次数的最大值。先统计每个数字出现的次数,然后判断相邻的下一个数字是否存在,存在则相加两个数出现的次数,更新最大值。
3.参考代码
方法一:排序+双指针
class Solution {
public int findLHS(int[] nums) {
Arrays.sort(nums);
int res = 0;
for (int i = 0, j = 0; i < nums.length; i++) {
while (nums[i] - nums[j] > 1) {
j++;
}
if (nums[i] - nums[j] == 1) {
res = Math.max(res, i - j + 1);
}
}
return res;
}
}
方法二:哈希计数
class Solution {
public int findLHS(int[] nums) {
Map<Integer, Integer> map = new HashMap<>();
int res = 0;
for (int num : nums) {
map.put(num, map.getOrDefault(num, 0) + 1);
}
for (int key : map.keySet()) {
int val = map.get(key);
if (map.containsKey(key + 1)) {
res = Math.max(res, val + map.get(key + 1));
}
}
return res;
}
}
128. 最长连续序列
1.题目描述
leetcode链接:128. 最长连续序列
2.思路分析
题目要求O(n)时间复杂度
方法一:排序,时间复杂度不满足O(nlogn)
方法二:哈希集合
3.参考代码
方法一:排序
class Solution {
public int longestConsecutive(int[] nums) {
int n = nums.length;
if (n < 2) return n;
int res = 1, count = 1;
Arrays.sort(nums);
for(int i = 0; i < n - 1; i++) {
if (nums[i + 1] == nums[i] + 1) {
count++;
} else if (nums[i + 1] == nums[i]) {
continue;
} else {
count = 1;
}
res = Math.max(res, count);
}
return res;
}
}
方法二:哈希集合
class Solution {
public int longestConsecutive(int[] nums) {
HashSet<Integer> set = new HashSet<>();
for (int num : nums) {
set.add(num);
}
int res = 0;
// 遍历去重后的所有数字
for (int num : set) {
int cur = num;
if (!set.contains(cur - 1)) {
// 只有当num-1不存在时,才开始向后遍历num+1,num+2,num+3
while (set.contains(cur + 1)) {
cur++;
}
}
// [num, cur]之间是连续的,数字有cur - num + 1个
res = Math.max(res, cur - num + 1);
}
return res;
}
}
注意:上述代码虽然有两层循环for+while,但是由于if (!set.contains(cur - 1))判断的存在,每个元素只会被遍历一次,因此时间复杂度也为O(n)。
674. 最长连续递增序列
1.题目描述
leetcode链接:674. 最长连续递增序列
2.思路分析
方法一:模拟判断
模拟,判断如果后一个元素大于前一个元素,则j++,否则记录最大值,j置为1。注意:最后再更新一下最大值,防止连续递增的情况。
方法二:动态规划
dp[i] 表示以i结尾的最大连续递增子序列长度。
3.参考代码
方法一:模拟判断
class Solution {
public int findLengthOfLCIS(int[] nums) {
int res = 1, j = 1;
for (int i = 1; i < nums.length; i++) {
if (nums[i] > nums[i - 1]) {
j++;
} else {
res = Math.max(res, j);
j = 1;
}
}
res = Math.max(res, j); // 连续递增
return res;
}
}
方法二:动态规划
class Solution {
public int findLengthOfLCIS(int[] nums) {
int n = nums.length;
int[] dp = new int[n];
int res = 1;
Arrays.fill(dp, 1);
for (int i = 1; i < n; i++) {
if (nums[i] > nums[i - 1]) {
dp[i] = dp[i - 1] + 1;
}
res = Math.max(res, dp[i]);
}
return res;
}
}
300. 最长递增子序列
1.题目描述
leetcode链接:300. 最长递增子序列
2.思路分析
与上一题不同的地方在于最长连续递增序列
是相邻的的递增序列,而最长递增子序列
不要求相邻,是子序列。
方法一:动态规划
上一题的判断条件是,当前元素比上一个元素大,则dp[i] = dp[i - 1] + 1
,与上一题不同的是当前元素比之前的元素大,dp[i] = Math.max(dp[i], dp[j] + 1);
方法二:贪心+二分查找
每次选择下一个递增元素的时候,都选择更小的结尾。使用二分查找,去找到这个更小的。
比如 {1, 5, 3, 4, 7} 5和3之间选择3作为当前递增序列的结尾元素。
dp[i]: 所有长度为i+1的递增子序列中, 最小的那个序列尾数。
对数组进行迭代, 依次判断每个数num将其插入dp数组相应的位置:
-
num > dp[res], 表示num比所有已知递增序列的尾数都大, 将num添加入dp数组尾部, 并将最长递增序列长度res加1
-
dp[i-1] < num <= dp[i], 只更新相应的dp[i]
3.参考代码
方法一:动态规划
class Solution {
public int lengthOfLIS(int[] nums) {
// 动态规划
int n = nums.length;
int[] dp = new int[n];
Arrays.fill(dp, 1);
int res = 0;
for (int i = 0; i < n; 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;
}
}
方法二:贪心+二分查找
class Solution {
public int lengthOfLIS(int[] nums) {
int n = nums.length;
int[] dp = new int[n];
int res = 0;
for (int num : nums) {
int i = 0, j = res;
while (i < j) {
int mid = (j - i) / 2 + i;
if (dp[mid] < num) {
i = mid + 1;
} else {
j = mid;
}
}
dp[i] = num;
if (res == j) res++;
}
return res;
}
}
1143. 最长公共子序列
1.题目描述
leetcode链接:1143. 最长公共子序列
2.思路分析
动态规划解决,dp[i][j]:长度为[0, i - 1]的字符串text1与长度为[0, j - 1]的字符串text2的最长公共子序列为dp[i][j]
转移方程:主要就是两大情况: text1[i - 1] 与 text2[j - 1]相同,text1[i - 1] 与 text2[j - 1]不相同
-
如果text1[i - 1] 与 text2[j - 1]相同,那么找到了一个公共元素,所以
dp[i][j] = dp[i - 1][j - 1] + 1;
-
如果text1[i - 1] 与 text2[j - 1]不相同,那就看看text1[0, i - 2]与text2[0, j - 1]的最长公共子序列 和 text1[0, i - 1]与text2[0, j - 2]的最长公共子序列,取最大的。即:
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
**初始化:**先看看dp[i][0]应该是多少呢?
test1[0, i-1]和空串的最长公共子序列自然是0,所以dp[i][0] = 0;
同理dp[0][j]也是0。
3.参考代码
class Solution {
public int longestCommonSubsequence(String text1, String text2) {
int m = text1.length(), n = text2.length();
int[][] dp = new int[m + 1][n + 1];
for (int i = 0; i <= m; i++) {
dp[i][0] = 0;
}
for (int j = 0; j <= n; j++) {
dp[0][j] = 0;
}
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][j - 1], dp[i - 1][j]);
}
}
}
return dp[m][n];
}
}
不同的初始化转移方程:
class Solution {
public int longestCommonSubsequence(String text1, String text2) {
int m = text1.length(), n = text2.length();
int[][] dp = new int[m + 1][n + 1];
for(int i = 0; i < m; i++) {
for(int j = 0; j < n; j++) {
if(text1.charAt(i) == text2.charAt(j)) {
dp[i + 1][j + 1] = dp[i][j] + 1;
}else {
dp[i + 1][j + 1] = Math.max(dp[i][j + 1], dp[i + 1][j]);
}
}
}
return dp[m][n];
}
}
516. 最长回文子序列
1.题目描述
leetcode链接:516. 最长回文子序列
2.思路分析
回文子序列与回文子串不同,回文子串是要连续的,回文子序列可不是连续的! 回文子串,回文子序列都是动态规划经典题目。
回文子串:647. 回文子串。
dp[i][j]:字符串s在[i, j]范围内最长的回文子序列的长度为dp[i][j]。
转移方程:
在判断回文子串的题目中,关键逻辑就是看s[i]与s[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]看看哪一个可以组成最长的回文子序列。
加入s[j]的回文子序列长度为dp[i + 1][j]。
加入s[i]的回文子序列长度为dp[i][j - 1]。
那么dp[i][j]一定是取最大的,即:dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);
初始化:
首先要考虑当i 和j 相同的情况,从递推公式:dp[i][j] = dp[i + 1][j - 1] + 2; 可以看出 递推公式是计算不到 i 和j相同时候的情况。
所以需要手动初始化一下,当i与j相同,那么dp[i][j]一定是等于1
的,即:一个字符的回文子序列长度就是1。
遍历顺序:
可以看出,dp[i][j]是依赖于dp[i + 1][j - 1] 和 dp[i + 1][j],
也就是从矩阵的角度来说,dp[i][j] 下一行的数据。 所以遍历i的时候一定要从下到上遍历,这样才能保证,下一行的数据是经过计算的。
3.参考代码
class Solution {
public int longestPalindromeSubseq(String s) {
int n = s.length();
int[][] dp = new int[n + 1][n + 1];
for (int i = n - 1; i >= 0; i--) { // 从后往前
dp[i][i] = 1;
for (int j = i + 1; j < n; 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][n - 1];
}
}
1218. 最长定差子序列
1.题目描述
leetcode链接:1218. 最长定差子序列
2.思路分析
动态规划+哈希
新的dp思路,与哈希表结合。将状态数组设置为哈希表, 对于每一个元素,判断其等差元素是否存在,不存在置为1, 等差元素个数加1。更新最长定差子序列。
常规DP:超时
对于每一个元素,判断与之前元素的定长关系,时间复杂度O(n^2)。
3.参考代码
动态规划+哈希
class Solution {
public int longestSubsequence(int[] arr, int difference) {
int res = 0;
HashMap<Integer, Integer> dp = new HashMap<>();
for (int ar : arr) {
dp.put(ar, dp.getOrDefault(ar - difference, 0) + 1);
res = Math.max(res, dp.get(ar));
}
return res;
}
}
常规DP:超时
class Solution {
public int longestSubsequence(int[] arr, int difference) {
int n = arr.length;
int res = 1;
int[] dp = new int[n];
Arrays.fill(dp, 1);
for (int i = 0; i < n; i++) {
for (int j = 0; j < i; j++) {
if (arr[i] - arr[j] == difference) {
dp[i] = Math.max(dp[j] + 1, dp[i]);
}
res = Math.max(res, dp[i]);
}
}
return res;
}
}
718. 最长重复子数组
1.题目描述
leetcode链接:718. 最长重复子数组
2.思路分析
动态规划
注意与最长公共子序列的差别,子序列不连续,子数组连续,所以子数组中dp[i + 1][j + 1] 只能由 dp[i][j]得到,dp[i + 1][j + 1] = dp[i][j] + 1;
而子序列不相等的时候仍然需要判断。
3.参考代码
动态规划
class Solution {
public int findLength(int[] nums1, int[] nums2) {
int m = nums1.length, n = nums2.length;
int[][] dp = new int[m + 1][n + 1];
int res = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (nums1[i] == nums2[j]) {
dp[i + 1][j + 1] = dp[i][j] + 1;
}
res = Math.max(res, dp[i + 1][j + 1]);
}
}
return res;
}
}
978. 最长湍流子数组
1.题目描述
leetcode链接:978. 最长湍流子数组
2.思路分析
方法一:统计前后两个相邻数字差
使用一个数组f[]标记arr[i] - arr[i +1]符号,
- 若小于0,记为-1
- 若大于0,记为1
- 若等于0,记为0
答案就是求连续 ···, 1, -1, 1, -1,···的最大长度。
方法二:
使用两个变量up和down分别表示上升和下降序列,初始化均为1
arr = [9,4,2,10,7,8,8,1,9]
arr[1] = 4 up = 1 down = 2 res = 2
arr[2] = 2 up = 1 down = 2 res = 2
arr[3] = 10 up = 3 down = 1 res = 3
arr[4] = 7 up = 1 down = 4 res = 4
arr[5] = 8 up = 5 down = 1 res = 5
arr[6] = 8 up = 1 down = 1 res = 5
arr[7] = 1 up = 1 down = 2 res = 5
arr[8] = 9 up = 3 down = 1 res = 5
3.参考代码
方法一:统计前后两个相邻数字差
class Solution {
public int maxTurbulenceSize(int[] arr) {
int n = arr.length;
if(n < 2) return n;
int[] f = new int[n - 1];
for(int i = 0; i < n - 1; i++) {
if(arr[i] < arr[i + 1]) {
f[i] = -1;
}else if(arr[i] == arr[i + 1]){
f[i] = 0;
}else {
f[i] = 1;
}
}
// 统计连续 ···, 1, -1, 1, -1,···的最大长度加1
int res = 1;
int count = f[0] == 0 ? 0 : 1;
for (int i = 0; i < n - 1; i++) {
if (i > 0 && f[i] != f[i - 1] && f[i] != 0) {
count++;
} else {
res = Math.max(res, count + 1);
count = f[i] == 0 ? 0 : 1;
}
}
res = Math.max(res, count + 1);
return res;
}
}
方法二
class Solution {
public int maxTurbulenceSize(int[] arr) {
int up = 1, down = 1;
int res = 1;
for(int i = 1; i < arr.length; i++) {
if(arr[i - 1] < arr[i] ) {
up = down + 1;
down = 1;
}else if(arr[i - 1] > arr[i]) {
down = up + 1;
up = 1;
}else {
up = down = 1;
}
res = Math.max(res, Math.max(up, down));
}
return res;
}
}
参考: