1143.最长公共子序列 1035.不相交的线 53.最大子序和动态规划

1143.最长公共子序列 1035.不相交的线 53.最大子序和动态规划

1143.最长公共子序列

力扣题目链接(opens new window)

给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。

若这两个字符串没有公共子序列,则返回 0。

示例 1:

  • 输入:text1 = “abcde”, text2 = “ace”
  • 输出:3
  • 解释:最长公共子序列是 “ace”,它的长度为 3。

示例 2:

  • 输入:text1 = “abc”, text2 = “abc”
  • 输出:3
  • 解释:最长公共子序列是 “abc”,它的长度为 3。

示例 3:

  • 输入:text1 = “abc”, text2 = “def”
  • 输出:0
  • 解释:两个字符串没有公共子序列,返回 0。

提示:

  • 1 <= text1.length <= 1000
  • 1 <= text2.length <= 1000 输入的字符串只含有小写英文字符。

思路

思路:动态规划
1.dp数组以及下标代表含义
定义二维数组dp[i][j].表示以字符串 text1[i] 和 text2[j]为结尾字符串中,最长公共子序列的长度。
我相信有人会对dp公式的定义存在疑问。
这样定义dp数组为什么不行?二维数组dp[i][j].表示以字符串 text1[i] 和 text2[j]为结尾的最长公共子序列的长度
可以用这种方式举个例子
求最长公共子序列的递推公式怎么写呢?
当nums[i] == nums[j]
dp[i][j] = dp[i-1][j-1] + 1 这没有问题
当nums[i] != nums[j]。
根据dp的定义,最长公共子序列dp[i][j]要以nums[i] 和 nums[j]为结尾。但是nums[i] != nums[j],所以dp[i][j] =0;
此时再看递推公式,这不就是求连续子序列的递推公式吗,我们要求的是不连续的子序列,所以dp数组的定义方式不可以定义【以字符串 text1[i] 和 text2[j]为结尾的最长公共子序列的长度】
2.确定递推公式
当nums[i] == nums[j]
dp[i][j] = dp[i-1][j-1] + 1
当nums[i] != nums[j]。两个字符串也可能存在最长公共子序列,
dp[i][j] =Math.max(dp[i][j-1],dp[i-1][j])
3.dp数组初始化
对dp数组第一行和第一列初始化
text1若有元素等于text2[0],则当前dp[i][0]以及第一行后面元素都置为1
text2若有元素等于text1[0],则当前dp[0][i]以及第一列后面元素都置为1
4.遍历顺序,dp[i]由dp[i-1]推导,故正序
5.举例推导dp数组
时间复杂度: O(n * m)
空间复杂度: O(n * m)

代码如下

public static void main(String srgs[]) {
    String abcde = "abcde";
    String ace = "ace";
    longestCommonSubsequence(abcde, ace);
}
public static int longestCommonSubsequence(String text1, String text2) {
    if (text1 == null || text2 == null)
        return 0;
    char[] t1 = text1.toCharArray();
    char[] t2 = text2.toCharArray();
    int[][] dp = new int[t1.length][t2.length];
    boolean flag = false;
    for (int i = 0; i < t1.length; i++) {// 初始化dp数组
        if (t1[i] == t2[0]) {
            dp[i][0] = 1;
            flag = true;
        }
        if(flag)
            dp[i][0] = 1;

    }
    flag = false;
    for (int j = 0; j < t2.length; j++) {
        if (t2[j] == t1[0]) {
            dp[0][j] = 1;
            flag = true;
        }
        if(flag)
            dp[0][j] = 1;
    }


    for (int i = 1; i < t1.length; i++) {
        for (int j = 1; j < t2.length; j++) {
            if (t1[i] == t2[j]) {
                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[t1.length-1][t2.length-1];
}

优化

二刷时采用另一种定义dp数组的方式
dp[i][j]:长度为[0, i - 1]的字符串text1与长度为[0, j - 1]的字符串text2的最长公共子序列为dp[i][j]
这样定义是为了后面代码实现方便,如果非要定义为长度为[0, i]的字符串text1也可以,,其实就是简化了dp数组第一行和第一列的初始化逻辑。

代码如下

public static int longestCommonSubsequence(String text1, String text2) {
    if (text1 == null || text2 == null)
        return 0;
    char[] t1 = text1.toCharArray();
    char[] t2 = text2.toCharArray();
    int[][] dp = new int[t1.length + 1][t2.length + 1];


    for (int i = 1; i <= t1.length; i++) {
        for (int j = 1; j <= t2.length; j++) {
            if (t1[i-1] == t2[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[t1.length][t2.length];
}

1035.不相交的线

力扣题目链接(opens new window)

我们在两条独立的水平线上按给定的顺序写下 A 和 B 中的整数。

现在,我们可以绘制一些连接两个数字 A[i] 和 B[j] 的直线,只要 A[i] == B[j],且我们绘制的直线不与任何其他连线(非水平线)相交。

以这种方法绘制线条,并返回我们可以绘制的最大连线数。

1143.最长公共子序列 1035.不相交的线 53.最大子序和动态规划_第1张图片

思路

思路:动态规划
动态规划五部曲
题目的本质是求两个字符串中,最长公共子序列的长度
绘制一些连接两个数字 A[i] 和 B[j] 的直线,只要 A[i] == B[j],且直线不能相交!
直线不能相交,这就是说明在字符串A中 找到一个与字符串B相同的子序列,且这个子序列不能改变相对顺序,只要相对顺序不改变,链接相同数字的直线就不会相交。
拿示例一A = [1,4,2], B = [1,2,4]为例,相交情况如图:
1143.最长公共子序列 1035.不相交的线 53.最大子序和动态规划_第2张图片

其实也就是说A和B的最长公共子序列是[1,4],长度为2。 这个公共子序列指的是相对顺序不变(即数字4在字符串A中数字1的后面,那么数字4也应该在字符串B数字1的后面)
这么分析完之后,大家可以发现:本题说是求绘制的最大连线数,其实就是求两个字符串的最长公共子序列的长度!
跟【最长公共子序列】的思路以及代码解法是一样的

1.定义dp数组以及下标含义
定义二维dp数组dp[i][j] 表示以nums1[i]和nums2[j]为结尾数组中,可以绘制的最大连线数
2.推导递推公式
if nums1[i] == nums2[j]
dp[i][j] = dp[i-1][j-1] + 1
if nums1[i] != nums2[j]
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
3.dp数组初始化
对dp数组第一行和第一列初始化
nums1[i]若有元素等于nums2[0],则当前dp[i][0]以及第一行后面元素都置为1
nums2[j]若有元素等于nums1[0],则当前dp[0][j]以及第一列后面元素都置为1
4.遍历顺序
由递推公式可得从小到大遍历
5.举例推导dp数组

代码如下

//时间复杂度: O(n * m)
//空间复杂度: O(n * m)
public static void main(String args[]) {
    int[] nums1 = new int[]{1, 3, 7, 1, 7, 5};
    int[] nums2 = new int[]{1, 9, 2, 5, 1};
    maxUncrossedLines(nums1, nums2);
}

public static int maxUncrossedLines(int[] nums1, int[] nums2) {
    if (nums1 == null || nums2 == null)
        return 0;

    int[][] dp = new int[nums1.length][nums2.length];// dp数组初始化
    if (nums1[0] == nums2[0])
        dp[0][0] = 1;
    for (int i = 1; i < nums1.length; i++) {
        dp[i][0] = dp[i - 1][0];
        if (nums1[i] == nums2[0] && dp[i - 1][0] == 0)
            dp[i][0] = 1;
    }
    for (int j = 1; j < nums2.length; j++) {
        dp[0][j] = dp[0][j - 1];
        if (nums2[j] == nums1[0] && dp[0][j - 1] == 0)
            dp[0][j] = dp[0][j - 1] + 1;
    }

    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;
            } else {
                dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
    }
    return dp[nums1.length - 1][nums2.length - 1];
}

53.最大子序和

力扣题目链接(opens new window)

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例:

  • 输入: [-2,1,-3,4,-1,2,1,-5,4]
  • 输出: 6
  • 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

思路

思路:动态规划
在dp数组定义上思考了半天
到底是定义【以i为结尾的连续子数组】,还是定义【以i为结尾的数组,从中找到最大得连续子数组】
看了题解才确定下来
其次递推公式也不好想
1.dp数组以及下标代表含义
定义dp[i]表示,以i为结尾的连续子数组(结尾为i)最大和
2.确定递推公式
题目要求找到最大和的连续子数组
以i为结尾的连续子数组最大和有两种方式求得
两种方式区别在于dp[i-1]值大于或小于0
第一种方式:dp[i] = dp[i-1] + nums[i] 这种方式容易想到.
举例如下: 1,2,3,-1 或者 1,2,3,4
第二种方式:dp[i] = nums[i] 不容易想到
举例如下:-1,-2,-3,-4 或者 -1,-2,-3,4
dp[i] = Math.max(dp[i-1] + nums[i],nums[i])
3.dp数组初始化
4.遍历顺序,dp[i]由dp[i-1]推导,故正序
5.举例推导dp数组
时间复杂度o(n)
空间复杂度o(n)

代码如下

public static void main(String args[]) {
    int[] nums1 = new int[]{-2, -1};
    maxSubArray(nums1);
}

public static int maxSubArray(int[] nums) {
    if (nums == null || nums.length == 0)
        return 0;
    int[] dp = new int[nums.length];
    dp[0] = nums[0];
    int result = nums[0];
    for (int i = 1; i < nums.length; i++) {
        dp[i] = Math.max(dp[i - 1] + nums[i], nums[i]);
        if(dp[i] > result)
            result = dp[i];
    }
    return result;
}

问题

二刷时,只能想出暴力算法的完整思路。动态规划只能想出Dp数组的定义【以i为结尾的连续子数组】
无法想出递推公式的解法
由于dp[i]的定义是【以i为结尾的连续子数组】,所以nums[i]肯定会加入dp[i].
dp[i] 和dp[i-1]有什么关联呢?
当dp[i-1]<=0,dp[i] = nums[i] ,当dp[i-1]>0,dp[i] = nums[i] + dp[i - 1]
一定是取最大的,所以dp[i] = max(dp[i - 1] + nums[i], nums[i]);

你可能感兴趣的:(算法,数据结构,leetcode,java)