算法刷题指北-基础篇-子序列问题

子序列问题

  • 子序列问题几乎都可以通过dfs进行处理;如果该问题具有无后效性,那么将可以通过动态规划处理,理由如下:

    • 子序列问题即对于串(或数组)的元素进行选择,无论是否具有后效性,其本质都可以通过暴力穷举所有选择,所以基于穷举的自底向上的求解,所以其必然可解该类问题;
    • 如果子序列问题具有无后效性,那么对于该问题即可使用记忆化矩阵进行搜索解空间将由n!变为n^x(x一般为2)
    • 对于可记忆化搜索的递归显然可以转化为迭代的动态规划;
      • 这里有俩个要点:
        • 子序列具有翻转对称性:所以f(x,y) = f(x+1,y+1) === f(x,y) = f(x-1,y-1)
        • dp的初值是起始条件而dfs初值是终止条件
  • 子序列问题和0-1背包问题几乎一样,所谓的子序列问题可能是:

    • 给定一个数组(逻辑数组、数值数组)
    • 每个数组的元素只有两种状态,被选入结果集和不选
      • 必须在当前状态下就可以确定是否选择进入结果集
    • 显著特征
      • 可选集是原数组的一部分
      • 选择后对可选集有显著影响(即判断条件)
逻辑数组子序列问题

例题

  • 逻辑数组:逻辑数组由于和数组数组的最大区别在于其结果集需要初始化(每次选择逻辑数组的一部分结果集都将确定一部分

    给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。
    --完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。
    
    --
    显然:小于n的平方数是确定的即:1*1~k*k ;其中k是小于根号n的最大正整数;所以其本身是一个逻辑数组;
    结果集就是:逻辑数组遍历到的当前元素到n
    --
    
    给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1-- 你可以认为每种硬币的数量是无限的。
    
    --
    显然:同理
    --
    
数值数组子序列问题
  • 数值数组:

    给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
    --子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
    
    
    --本题可以通过贪心实现(每次选跳跃最小的)
    
    给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。
    --连续递增的子序列 可以由两个下标 l 和 r(l < r)确定,如果对于每个 l <= i < r,都有 nums[i] < nums[i + 1] ,那么子序列 [nums[l], nums[l + 1], ..., nums[r - 1], nums[r]] 就是连续递增子序列。
    
    --本题直接遍历即可
    
  • 这类问题共同特点除了符合dp三要素,共同特点是:所有元素只有两种可能:选或者不选,而且选择后对结果集的影响具有显著特征(类似0-1背包)

    • 279:选择后影响的唯一特征是dp[价值]=dp[价值-选择的数]+1
    • 300:dp[价值]=dp[选择的数]+1
    • 674:dp[价值]=dp[前一个]+1
for(遍历数组) {
    未选择前的初始化
	for(遍历可选结果集) {
     	if(符合选择条件) {
            dp[i] = 修改
        }   
    }
}


//279
	//一般dp方法
int[] dp = new int[n+1];
for(int i=1;i<dp.length;i++) {
    dp[i] = Integer.MAX_VALUE-1;
    for(int j=1;j*j<=i;j++) {
        dp[i] = Math.min(dp[i],dp[i-j*j]);
    }
    dp[i]++;
}
return dp[n];

	//逻辑数组dp
int[] dp = new int[n+1];
for(int i=1;i<=n;i++) {
    dp[i] = Integer.MAX_VALUE-1;
}
for(int i=1,k=1;k<=n;k=i*i) {
    for(int j=k;j<=n;j++) {
        dp[j] = Math.min(dp[j],dp[j-k]+1);
    }
    i++;
}
return dp[n];



// Offer II 103(一般dp就省略)
	//逻辑数组dp
int[] dp = new int[amount+1];
for(int i=1;i<dp.length;i++) {
    dp[i] = Integer.MAX_VALUE-1;
}
for(int i=0;i<nums.length;i++) {
    for(int j=nums[i];j<=amount;j++) {
        dp[j] = Math.min(dp[j],dp[j-nums[i]]+1);
    }
}
return dp[amount]==Integer.MAX_VALUE-1?-1:dp[amount];






//300
int[] dp = new int[nums.length];
int res = 0;
for(int i=0;i<dp.length;i++) {
    dp[i] = 0;
    for(int j=1;j<=i;j++) {
        if(nums[i]>nums[i-j]) {
            dp[i] = Math.max(dp[i],dp[i-j]);
        }
    }
    dp[i]++;
    res = Math.max(dp[i],res);
}
return res;

//674
int[] dp = new int[nums.length];
int res = 0;
for(int i=0;i<dp.length;i++) {
    dp[i] = 0;
    //for(int j=i;j<=i;j++) {
        if(i>0&&nums[i]>nums[i-1]) {
            dp[i] = Math.min(dp[i],dp[i-j*j]);
        }
    //}
    dp[i]++;
    res = Math.max(dp[i],res);
}
return res;


字符串子序列问题
双向删除
  • 双向:两个字符串
  • 删除:可以删除两者中任意一个的任意字符串

特征

  • 这种问题和单串子序列问题的区别最大的区别在于:可选集
    • 单串子序列问题中,可选集完全取决前面显著条件的影响,换句话说就是:可选集在前面已经直接被确定;或者说遍历到x的时候,已经可以确定是否选择x
    • 双向选择:
      • 关键在于双选选择,在本轮选择中,不能直接决定后面的选择;;体现必须考虑下一阶段不可选条件下的情况","也就是说在当前选择不可选的时候,必须进行处理以确定最佳值(由于可选集不是数组一部分)
        • 解决思路:反向选择使可选集是两数组组合的一部分(变为前面子序列问题):
          • A[i]->B[j]反向选择为:B[j]->A[i];问题变为两个单向选择问题
      • 单串子序列问题,不可选不需要处理,本身已经由前面直接确定(体现在可选集本身就是确定的);

例题

  • 1035. 不相交的线

    在两条独立的水平线上按给定的顺序写下 nums1 和 nums2 中的整数。
    现在,可以绘制一些连接两个数字 nums1[i] 和 nums2[j] 的直线,这些直线需要同时满足满足:
    	1.nums1[i] == nums2[j]
    	2.且绘制的直线不与任何其他连线(非水平线)相交。
    	3.端点也不能相交:每个数字只能属于一条连线。
    --以这种方法绘制线条,并返回可以绘制的最大连线数。
    
    --
    f(x,y)定义为:可选数组为x、y下的最大连线
    --
    
  • 718. 最长重复子数组

    给两个整数数组 nums1 和 nums2 ,返回 两个数组中 公共的 、长度最长的子数组的长度 。
    --子数组:截取 1<=i<=j<=length中,i~j部分
    
    --
    f(x,y)定义为: 可选数组在x、y的最长公共长度
    --
    
  • 1143. 最长公共子序列

    给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 
    -- 一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
    --例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。
    --两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。
    
    --
    f(x,y)定义为:可选数组为x、y下的最大公共子序列
    --
    

解题

for(遍历子串1) {
    for(遍历子串2) {
        if(子串1选择部分==子串2选择部分) {
            选择操作
        } else {
        	处理部分
        }
    }
}

//1035. 不相交的线
        int[][] dp = new int[2][nums2.length+1];
        for(int i=0;i<nums1.length;i++) {
            for(int j=1;j<=nums2.length;j++) {
                if(nums1[i]==nums2[j-1]) {
                    dp[1][j] = dp[0][j-1]+1;
                } else {
                    dp[1][j] = Math.max(dp[0][j],dp[1][j-1]);
                }
            }
            int[] swap = dp[0];
            dp[0] = dp[1];
            dp[1] = swap;
        }
        return dp[0][nums2.length];


//1143. 最长公共子序列
        char[] nums1 = text1.toCharArray();
        char[] nums2 = text2.toCharArray();
        int[][] dp = new int[2][nums2.length+1];
        for(int i=0;i<nums1.length;i++) {
            for(int j=1;j<=nums2.length;j++) {
                if(nums1[i]==nums2[j-1]) {
                    dp[1][j] = dp[0][j-1]+1;
                } else {
                    dp[1][j] = Math.max(dp[0][j],dp[1][j-1]);
                }
            }
            int[] swap = dp[0];
            dp[0] = dp[1];
            dp[1] = swap;
        }
        return dp[0][nums2.length];

//718
        int[] dp = new int[nums2.length+1];
        int res = 0;
        for(int i=0;i<nums1.length;i++) {
            for(int j=nums2.length;j>0;j--) {
                if(nums1[i]==nums2[j-1]) {
                    dp[j] = dp[j-1]+1;
                    res = Math.max(res,dp[j]);
                } else {
                    dp[j] = 0;
                }
            }
        }
        return res;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

舔猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值