6.6 动态规划子序列问题

最长重复子数组

注意:子数组是指连续序列

思想:

1.定义dp[i][j]为 以下标i为结尾的A,和以下标j为结尾的B,最长重复子数组长度为dp[i][j]

2.确定递推公式:当A[i - 1] 和B[j - 1]相等的时候,dp[i][j] = dp[i - 1][j - 1] + 1;

3.初始化:这里的初始化有些特殊,是nums[0]==nums[j]时候dp[0][j]=1,其余情况为0。dp[i][0]同理。

 public int findLength(int[] nums1, int[] nums2) {
    	int[][] dp=new int[nums1.length][nums2.length];
    	if(nums1[0]==nums2[0])dp[0][0]=1;
    	else dp[0][0]=0;
        //两个for循环初始化
    	for(int j=1; j<nums2.length; j++) {
    		if(nums2[j]==nums1[0])dp[0][j]=1;
    	}
    	for(int i=1; i<nums1.length; i++) {
    		if(nums1[i]==nums2[0])dp[i][0]=1;
    	}
    	
    	int res=0;
        //遍历dp数组
    	for(int i=1; i<nums1.length; i++)
    		for(int j=1; j<nums2.length; j++) {
    			if(nums1[i]==nums2[j])dp[i][j]=dp[i-1][j-1]+1;
    		}
        //找出最大的dp
            for(int i=0; i<nums1.length; i++)
                for(int j=0; j<nums2.length; j++)res=Math.max(dp[i][j], res);
    	return res;
    }

这里我的dp数组是以i、j结尾的子序列,其实这样会导致初始化有些复杂。

dp数组应该定义为下标i-1为结尾的A,和以下标j-2为结尾的B,最长重复子数组长度为dp[i][j]

这样初始化第一行和第一列就都是0,就不需要专门去初始化了,并且在找最大的res时候,只需要在填充dp的时候进行比较,而不需要专门去循环dp数组找出最大的数,如下所示:

    public int findLength(int[] nums1, int[] nums2) {
    	int[][] dp=new int[nums1.length+1][nums2.length+1];
    	
    	int res=0;
    	for(int i=1; i<=nums1.length; i++)
    		for(int j=1; j<=nums2.length; j++) {
    			if(nums1[i-1]==nums2[j-1])dp[i][j]=dp[i-1][j-1]+1;
    			res=Math.max(res, dp[i][j]);
    		}
    	return res;
    }

可以看到代码简洁很多,并且复杂度减小了。

最长公共子序列 && 不相交的线

思想:

1.dp[i][j]:   长度为[0, i - 1]的字符串text1与长度为[0, j - 1]的字符串text2的最长公共子序列为dp[i][j]

2.状态方程:

如果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]);(这是连续和不连续子序列最大的区别

3.初始化:text1[0, i-1]和空串的最长公共子序列自然是0,所以dp[i][0] = 0;

同理dp[0][j]也是0。

    public int longestCommonSubsequence(String text1, String text2) {
    	int[][] dp=new int[text1.length()+1][text2.length()+1];
    	for(int i=1; i<=text1.length(); i++)
    		for(int j=1; j<=text2.length(); 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-1][j], dp[i][j-1]);
				}
    		}
    	return dp[text1.length()][text2.length()];
    }

最长递增子序列

思想:

1.确定dp:dp[i]表示以nums[i]结尾的最长递增子序列

2.状态方程:

位置i的最长升序子序列等于j从0到i-1各个位置的最长升序子序列 + 1 的最大值。

所以:if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);

3.初始化:每一个i,对应的dp[i](即最长上升子序列)起始大小至少都是1(数字本身)

    public int lengthOfLIS(int[] nums) {
    	int[] dp=new int[nums.length];
    	Arrays.fill(dp, 1);
    	for(int i=1; i<nums.length; i++){
    		for(int j=0; j<i; j++) {
    			if(nums[i]>nums[j])dp[i]=Math.max(dp[j]+1, dp[i]);
    		}
        }
    	Arrays.sort(dp);
    	return dp[nums.length-1];
    }

最长连续递增子序列

思想:相比上一道多一个连续的条件

1.dp[i]:以下标i为结尾的数组的连续递增的子序列长度为dp[i]。

2.状态方程:

如果 nums[i + 1] > nums[i],那么以 i+1 为结尾的数组的连续递增的子序列长度 一定等于 以i为结尾的数组的连续递增的子序列长度 + 1 。

即:dp[i + 1] = dp[i] + 1;

3.初始化:最小的长度就是数字本事,为1

     public int findLengthOfLCIS(int[] nums) {
    	int[] dp=new int[nums.length];
    	Arrays.fill(dp, 1);
    	for(int i=1; i<nums.length; i++) {
    		if(nums[i]>nums[i-1])dp[i]=dp[i-1]+1;
    	}
    	Arrays.sort(dp);
    	return dp[nums.length-1];
    }

最大子序和

思想:为连续的最大子序列和,无非就是两种情况前面的连续序列加上nums[i],单独一个nums[i]

1. 确定dp:以nums[i]结尾的子序列的最大和为dp[i]

2.状态方程:dp[i-1]<0: dp[i]=nums[i]

                     dp[i-1]>=0: dp[i]=dp[i-1]+nums[i]

3.初始化

判断子序列

思想:这个和最长子序列一样,只需要判断最后的最长的长度是否等于较小序列s的长度即可

    public boolean isSubsequence(String s, String t) {
    	int[][] dp=new int[s.length()+1][t.length()+1];
    	for(int i=1; i<=s.length(); i++)
    		for(int j=1; j<=t.length(); j++) {
    			if(s.charAt(i-1)==t.charAt(j-1))dp[i][j]=dp[i-1][j-1]+1;
    			else {
					dp[i][j]=Math.max(dp[i-1][j], dp[i][j-1]);
				}
    		}
    	return dp[s.length()][t.length()]==s.length() ? true : false;
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值