前言
动归典型的子问题分解,配合程序的循环和递归,简洁的代码就能完成问题的解答。
动归三类(个人总结),
1-状态不断更新,求得最后一个状态为结果。基础型。
2-状态不断更新,很多个状态,选择其中一个状态,如选最长、最小。中等型。
3-状态不断更新,但不是用的上一个状态,而是前面很多状态中的一个状态。如背包问题。动归本质型,它体现的动归的本质–记忆化数组–目的就是剪枝–空间换时间。
状态压缩三细节,
1-用到的是上一层状态,所以可状态压缩。
2-用到的是上一层且上一个,所以需要反向更新,做到不覆盖。
3-上下层公用一个数组,就必须做到每个值都覆盖,比如continue时一定注意。
一、最长重复子数组
二、动归
1、动归第二类
//最长重复子数组
public class FindLength {
/*
target:求两个数组的公共最长子数组。
如何求公共子数组?把数组分为子数组,然后给子数组增加一个元素,那么新数组和另一个数组的公共长度就和旧子数组有关,和旧数组以最后一个元素为尾的公共子数组。
如何得到公共最长?用一个max变量来记录所有公共数组的最长长度。
*/
public int findLength(int[] nums1, int[] nums2) {
int max = 0;
int m = nums1.length, n = nums2.length;
int[][] dp = new int[m + 1][n + 1];
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
//以第j个数字结尾的公共子数组长度。
dp[i][j] = nums1[i - 1] == nums2[j - 1] ? dp[i - 1][j - 1] != 0 ? 1 + dp[i - 1][j - 1] : 1 : 0;
if (max < dp[i][j]) max = dp[i][j];
}
}
for (int[] ints : dp) {
for (int anInt : ints) {
System.out.print(anInt);
}
System.out.println();
}
return max;
}
/*
dp三类,
1-状态不断更新,求得最后一个状态为结果。基础型
2-状态不断更新,很多个状态,选择其中一个状态,如选最长、最小。中等型。
3-状态不断更新,但不是用的上一个状态,而是前面很多状态中的一个状态。如背包问题。动归本质型,它体现的动归的本质--记忆化数组--目的就是剪枝--空间换时间。
*/
}
2、状态压缩
//状态压缩
class FindLength2 {
//动归典型的状态压缩,原因在于它只利用了上一层的状态,不像背包纯纯的记忆化数组。
public int findLength(int[] nums1, int[] nums2) {
int max = 0, m = nums1.length, n = nums2.length;
int[] dp = new int[n + 1];
for (int i = 1; i <= m; i++) {
for (int j = n; j >= 1; j--) {//用到的不仅是上一层的状态,而且是前一个状态,所以当前状态计算应该倒着覆盖上一层。
//bug1;使用continue的话,就要先把旧状态赋值为0
dp[j] = 0;
if (nums1[i - 1] != nums2[j - 1]) continue;
dp[j] = 1;
if (dp[j - 1] != 0) dp[j] += dp[j - 1];
if (max < dp[j]) max = dp[j];
}
}
return max;
}
}
总结
1)动归练习
2)状态压缩
参考文献
[1] LeetCode 最长重复子数组