子序列问题
最长递增自序列

动态规划
状态表示 :
dpi:以i位置结尾,所有自序列中,最长长度
状态转移方程 :
长度为1 1
长度大于1 j取值范围 0,i-1,求出 j 结尾中的最长子序列长度+1就是dpi的结果
初始化 :
此时最小长度为1,此时可以将dp表初始化为1,不用考虑长度为1的情况
填表顺序 :从左到右
返回值:dp表中最大值


java
class Solution {
public int lengthOfLIS(int[] nums) {
int n = nums.length;
int[] dp = new int[n];
for (int i = 0; i < n; i++) {
dp[i] = 1;//最小为1
}
int ret = 1;
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);
}
}
//更新结果
ret = Math.max(ret,dp[i]);
}
return ret;
}
}
摆动序列

题目解析:求最长摆动序列长度,就是两个元素差值是一正一负情况,也就是递增和递减先后出现
状态表示 :
fi : 以i位置结尾元素子序列中,最后呈现"上升"趋势最长摆动序列长度
gi : 以i位置结尾元素子序列中,最后呈现"下降"趋势最长摆动序列长度
状态转移方程 :
长度为1 1
长度大于1 j取值范围 0,i-1 ,求出满足摆动序列最大长度
numsj < numsi -> fi = max(fi , gj + 1)
numsj > numsi -> gi = max(gi , fj + 1)
初始化 :
此时最小长度为1,此时可以将f和g表初始化为1,不用考虑长度为1的情况
填表顺序 :从左到右
返回值:f和g表中最大值


java
class Solution {
public int wiggleMaxLength(int[] nums) {
int n = nums.length;
int[] f = new int[n];//结尾是上升
int[] g = new int[n];//结尾是下降
for(int i = 0;i < n;i++){
f[i] = g[i] = 1;
}
int ret = 1;
for(int i = 1;i < n;i++){
for(int j = 0;j < i;j++){
if(nums[j] < nums[i]){
f[i] = Math.max(f[i],g[j]+1);
}else if(nums[j] > nums[i]){
g[i] = Math.max(g[i],f[j]+1);
}
}
ret = Math.max(Math.max(f[i],g[i]),ret);
}
return ret;
}
}
最长递增子序列的个数

题目解析:找出最长递增子序列的个数,上面已经知道如何找最长子序列了
状态表示 :
leni : 以i位置结尾元素子序列中,最长递增子序列的"长度"
counti : 以i位置结尾元素子序列中,最长递增子序列的"个数"
状态转移方程 :
长度为1 1
长度大于1 j取值范围 0,i-1 ,numsi > numsj 找出最长递增子序列长度及其个数
if(lenj+1 == leni){
//更新最大长度的值
counti += countj;
//最长长度发生变化
}else if(lenj + 1 > leni){
leni = lenj+1;
counti = countj;//重新计数
}
初始化 :
最长递增子序列长度为1,出现此时最长递增子序列个数最小为1,
填表顺序 :从左到右
返回值:需要找出len表中最大长度,并找出最大长度出现次数count表、


java
class Solution {
public int findNumberOfLIS(int[] nums) {
int n = nums.length;
int[] len = new int[n];//最长递增子序列长度
int[] count = new int[n];//最长递增子序列出现的个数
for(int i = 0;i < n;i++){
len[i] = count[i] = 1;
}
int retlen = 1;
int retcount = 1;
for(int i = 1;i < n;i++){
for(int j = 0;j < i;j++){
if(nums[j] < nums[i]){
//长度一样
if(len[j]+1 == len[i]){
//更新最大长度的值
count[i] += count[j];
//最长长度发生变化
}else if(len[j] + 1 > len[i]){
len[i] = len[j]+1;
count[i] = count[j];//重新计数
}
}
}
if(retlen == len[i]){
retcount += count[i];
}else if(retlen < len[i]){//跟新最长长度,重新计数
retlen = len[i];
retcount = count[i];
}
}
return retcount;
}
}
最长数对链

题目解析:最长数对链长度,当一个数对的头大于一个数对的尾,可以将其跟随到尾部,此时这里是任意选择
因为这里任意选择,导致填表需要根据其前后两端进行填表,无法确定,此时可以先根据其数对中第一个元素进行从小到大排序,这样只需要根据当前位置之前的值进行填表即可,其排序后,其前面元素是无法插在当前数对的尾部
动态规划(先排序)
状态表示 :
dpi:以i位置元素结尾, 最长数对链路长度
状态转移方程 :
长度为1 1
长度大于1 j取值范围 0,i-1,求出 j 结尾中的dpj+1最大值就是dpi的结果
初始化 :
此时最小长度为1,此时可以将dp表初始化为1,不用考虑长度为1的情况
填表顺序 :从左到右
返回值:dp表中最大值


java
class Solution {
public int findLongestChain(int[][] pairs) {
int n = pairs.length;
//根据数对中第一个元素进行排序,这样每次只根据前面更新dp表即可
Arrays.sort(pairs,(a,b) -> a[0] - b[0]);
int[] dp = new int[n];
for(int i = 0;i < n;i++){
dp[i] = 1;
}
int ret = 1;
for(int i = 1;i < n;i++){
for(int j = 0;j < i;j++){
//尾 < 头
if(pairs[j][1] < pairs[i][0]){
dp[i] = Math.max(dp[i],dp[j]+1);
}
}
ret = Math.max(ret,dp[i]);
}
return ret;
}
}
最长定差子序列

题目解析:找出最长定差子序列长度
动态规划
状态表示 :
dpi:以i位置元素结尾, 最长定差子序列长度
状态转移方程 :
当前元素是a
找是否存在 a - difference,没有就是1
找到可能有多个,但是只需要最后一个即可,因为其长度是最长的
初始化 :
此时最小长度为1,此时可以将dp表初始化为1,不用考虑长度为1的情况
填表顺序 :从左到右
返回值:dp表中最大值


java
这里因为其定差是确定的,可以使用哈希表将其arr[i],和 dp[i]进行绑定
其长度为数组中 其key: arr[i]-difference value:长度+1就是arr[i]对应的值
但是这里可能找不到,找不到,自己构成,长度为1
遍历数组,找其arr[i]-difference长度进行更新,数组是从前向后,其到后面结果会覆盖前面,符合最长长度(最后一个是最长)
java
class Solution {
public int longestSubsequence(int[] arr, int difference) {
//使用哈希表,用其做dp
Map<Integer,Integer> hash = new HashMap<>(); //arr[i] dp[i]
int ret = 1;
for(int a : arr){
//以arr[i]结尾的定差长度
//没有就是1
//这里需要最长的,因此最长的是arr数组中最后一个a-difference == b
//这里从前向后,相同值会覆盖,所以最终结果就是最长
hash.put(a,hash.getOrDefault(a-difference,0) + 1);
ret = Math.max(ret,hash.get(a));
}
return ret;
}
}
最长的斐波那契子序列的长度

题目解析:找出最长斐波那契子序列长度,满足斐波那契就是 arri + arri+1 == arri+2,一个序列所有元素都满足
动态规划
状态表示 :
dpij:以i位置元素及其j位置元素结尾, 最长斐波那契子序列长度(i < j)
状态转移方程 :
a = arrj - arri找是否存在这个数
存在元素a a < arri dpij = dpki + 1
存在元素a arri <a < arrj - > 2
不存在a 2
初始化 :
将dp表中都初始化为2,简化状态转移方程填写
填表顺序 :从上到下
返回值 :dp表中最大值
可以将所有元素及其下标对应关系放到哈希表中,这样查找效率高



java
class Solution {
public int lenLongestFibSubseq(int[] arr) {
int n = arr.length;
int[][] dp = new int[n][n];//以i,j结尾最长斐波那契子序列长度
Map<Integer,Integer> hash = new HashMap<>();
//将值和下标绑定
//这里是严格递增不会有重复元素
for(int i = 0;i < n;i++){
hash.put(arr[i],i);
}
for(int i = 0;i < n;i++){
for(int j = 0;j < n;j++){
dp[i][j] = 2;
}
}
int ret = 2;
for(int j = 2;j < n;j++){//固定最后一个数
for(int i = 1;i < j;i++){//倒数第二个数
int x = arr[j] - arr[i];//需要找的值
//找到这个数,并且其位置是在i下标之前
if(x < arr[i] && hash.containsKey(x)){
dp[i][j] = dp[hash.get(x)][i] + 1;
ret = Math.max(ret,dp[i][j]);
}
}
}
//可能构不成斐波那契子序列
return ret < 3 ? 0 : ret;
}
}
最长等差数列

题目解析:求最长等差子序列长度
动态规划
和上题一样,需要知道不仅需要最后一个数也需要倒数第二个数,依旧使用二维数组
dpij:以i位置元素及其j位置元素结尾子序列中, 最长等差序列长度(i < j)
状态转移方程 :
a = arrj - arri找是否存在这个数,有的话假设下标为k
存在元素a k < i dpij = dpki + 1
存在元素a i <= k <= j 2
不存在a 2
初始化 :
将dp表中都初始化为2,简化状态转移方程填写
填表顺序 :
有要求,1.固定倒数第二个数,枚举倒数第一个数,从哈希表中找符合等差的数(为了正确使用哈希表)
返回值 :dp表中最大值
使用一个哈希表
一边dp,一边将最近元素<元素,下标>放入哈希表中
当i位置(倒数第二个数)填写之后,将i位置对应元素和下标放入哈希表中
因为i每次固定,所以这里哈希表中存放就是最近的,并且使用不会错
如果是固定最后一个数,枚举倒数第二个数,其i不断变化,离i倒数第二个数最近元素下标不但变化,会导致错误



java
class Solution {
public int longestArithSeqLength(int[] nums) {
int n = nums.length;
int[][] dp = new int[n][n];
Map<Integer, Integer> hash = new HashMap<>();
//初始化
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
dp[i][j] = 2;
}
}
hash.put(nums[0],0);
int ret = 2;
for (int i = 1; i < n; i++) {//先确定倒数第二个数
for (int j = i + 1; j < n; j++) {//枚举最后一个数
int x = 2 * nums[i] - nums[j];
//哈希表中找出一个满足等差的数
if(hash.containsKey(x)){
dp[i][j] = dp[hash.get(x)][i] + 1;
ret = Math.max(ret,dp[i][j]);
}
}
//将其放入哈希表中
hash.put(nums[i],i);
}
return ret;
}
}
等差数列划分||-子序列

题目解析:求出等差序列的个数
这里不仅要知道等差序列的长度,也要知道等差序列对应的序列,这样后续填表才可以知道是否可以构成等差数列
动态规划
状态表示
dpij:以i位置元素及其j位置元素结尾子序列中, 等差序列的个数(i < j)
状态转移方程 :
a = 2 * arri - arrj找出所有符合要求k下标,因为都可以构成等差序列,都要加上
存在元素a 所有的符合要求k下标 k < i dpij = dpki + 1
存在元素a 下标不符合要求 0
不存在a 0
初始化 :
将dp表中都初始化为0,至少3个元素才可以构成等差序列
填表顺序 :
固定倒数第一个数,枚举倒数第二个数,从哈希表中找符合等差的数
返回值 :dp表的总和
优化找符合等差序列的数 a
使用哈希表<元素,下标数组> ,dp之前,将其元素和下标数组(一个元素可能有多个)对应关系进行绑定



这里的数虽然不会溢出,但是其运算过程中可能会溢出,因此这里哈希表中类型改为long类型

java
class Solution {
public int numberOfArithmeticSlices(int[] nums) {
int n = nums.length;
int[][] dp = new int[n][n];
int[] pairs = new int[n];
//使用long类型,计算过程中可能会溢出
Map<Long,List<Integer>> hash = new HashMap<>();
//将值和下标数组对应
for(int i = 0;i < n;i++){
long tem = (long)nums[i];
if( !hash.containsKey(tem)){
hash.put(tem,new ArrayList<>());
}
hash.get(tem).add(i);
}
int sum = 0;
for(int j = 2;j < n;j++){//倒数第一个数
for(int i = 1;i < j;i++){//倒数第二个数
long x = 2L * nums[i] - nums[j];
if(hash.containsKey(x)){
//所有符合要求的下标
for(int k : hash.get(x)){
if(k < i){
dp[i][j] += dp[k][i] + 1;
}else{
//下标是递增存储,不符合要求后面也都不符合
break;
}
}
sum += dp[i][j];
}
}
}
return sum;
}
}