LeetCode DP篇-求子序列问题(1143、300、53、72)

1143. 最长公共子序列

给定两个字符串 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:暴力,找出其中一个string的所有子序列,然后拿去第二个进行匹配,匹配到即为公告子序列
//思路2:动态规划,将两个String当初二维数组的行列,从两个String的第一个字符关系进行比较,逐渐增加字符个数,递推进行

solution1 dp

class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
        int m = text1.length();
        int n = text2.length();
        int[][] dp = new int[m+1][n+1];
        for (int i=1; i<m+1; i++){ 
            for (int j=1; j<n+1; j++){ 
                if (text1.charAt(i-1) == text2.charAt(j-1)) {
                    dp[i][j] = dp[i-1][j-1] + 1; //(i,j)=(i-1,j-1),因此从1开始遍历到m+1
                }else{
                    dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]);
                }
            }
        }
        return dp[m][n];
    }
}

dp2

//通过将字符串转化为char数组提升性能
class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
        char[] chars1 = text1.toCharArray();
        char[] chars2 = text2.toCharArray();
        int m = text1.length();
        int n = text2.length();
        int[][] dp = new int[m+1][n+1];
        for(int i = 1; i < m+1; i++){
            for(int j = 1; j < n+1; j++){
                if (chars1[i-1] == chars2[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[m][n];
    }
}

最长公共子串

class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
        char[] chars1 = text1.toCharArray();
        char[] chars2 = text2.toCharArray();
        int m = text1.length();
        int n = text2.length();
        int[][] dp = new int[m+1][n+1];
        int max = 0;
        for(int i = 1; i < m+1; i++){
            for(int j = 1; j < n+1; j++){
                if (chars1[i-1] == chars2[j-1]){
                    dp[i][j] = dp[i-1][j-1] + 1; 
                    max = Math.max(max,dp[i][j]);
                }
            }
        }
        return max;
    }
}

300. 最长上升子序列

给定一个无序的整数数组,找到其中最长上升子序列的长度。

示例:

输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:

可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?

solution1 普通动态规划

class Solution {
    public int lengthOfLIS(int[] nums) {
        int[] dp = new int[nums.length];
        Arrays.fill(dp,1);
        for (int i = 0; i < nums.length; i++){
            for (int j = 0; j < i; j++){
                if(nums[j]<nums[i]) dp[i] = Math.max(dp[i],dp[j]+1);
            }
        }
        int res = 0;
        for(int i = 0; i < nums.length; i++){
            res = Math.max(res,dp[i]);
        }
        return res;
    }
}
//普通dp
//1.dp数组
//2.求base case
//2.数学归纳法求dp[i],即确定状态转移方程

solution2 优化dp

class Solution {
    public int lengthOfLIS(int[] nums) {
        int[] top = new int[nums.length];
        int piles = 0;
        for (int i = 0; i < nums.length; i++){
            int left = 0, right = piles;
            int curr = nums[i];
            //二分查找,查找小于当前且最大的数
            while(left < right){
                int mid = (left + right) >> 1;
                if (top[mid] > curr){
                    right = mid;
                }else if(top[mid] < curr){
                    left = mid + 1;
                }else{
                    right = mid;
                }
            }
            //牌堆最大值小于当前,新建堆
            if (left == piles) piles ++;
            top[left] = curr;
        }
        return piles;
    }
}
//优化dp
//在普通dp状态方程的求解上,利用二分查找,将时间复杂度缩小到 O(log N)
//参考资料:https://leetcode-cn.com/problems/longest-increasing-subsequence/solution/dong-tai-gui-hua-she-ji-fang-fa-zhi-pai-you-xi-jia/

53. 最大子序和

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

示例:

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

如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。

class Solution {
    public int maxSubArray(int[] nums) {
        int[] dp = new int[nums.length];
        dp[0] = nums[0];
        for(int i = 1; i < nums.length; i++){
            dp[i] = Math.max(nums[i],nums[i]+dp[i-1]);
        }
        int res = Integer.MIN_VALUE;
        for(int j = 0; j < nums.length; j++){
            res = Math.max(dp[j],res);
        }
        return res;
    }
}

//!!!考虑每一数加不加入之前还是另起数组

//1、dp数组,每一状态保存有当前数加入的最大和子串
//2、dp[0] = nums[0]
//3、状态转移:max(nums[i],nums[i]+dp[i-1])

72. 编辑距离

给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。

你可以对一个单词进行如下三种操作:

插入一个字符
删除一个字符
替换一个字符

示例 1:

输入:word1 = "horse", word2 = "ros"
输出:3
解释:
horse -> rorse (将 'h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')
示例 2:

输入:word1 = "intention", word2 = "execution"
输出:5
解释:
intention -> inention (删除 't')
inention -> enention (将 'i' 替换为 'e')
enention -> exention (将 'n' 替换为 'x')
exention -> exection (将 'n' 替换为 'c')
exection -> execution (插入 'u')

solution1 暴力递归

class Solution {
    public int minDistance(String word1, String word2) {
        char[] c1 = word1.toCharArray();
        char[] c2 = word2.toCharArray();
        return helper(c1,c2,c1.length-1,c2.length-1);
    }
    public int helper(char[] c1, char[] c2, int n, int m){
        if (n == -1) return m+1;
        if (m == -1) return n+1;
        
        if (c1[n] == c2[m]){
            return helper(c1,c2,n-1,m-1); //相同不用改变
        }else{ //删除、插入、交换改变最小的那一个
            return Math.min(helper(c1,c2,n-1,m)+1,
                            Math.min(helper(c1,c2,n,m-1)+1,
                            helper(c1,c2,n-1,m-1)+1)
                            );
        }
    }

}
// String st = String.valueOf(c);
// char[] c = st.toCharArray();
//类似最长公共子序列
//思路1:暴力递归 找出每个操作中删除、插入、交换改变最小的那一个

solution2 带备忘录的递归(自顶向下)

class Solution {
    public int minDistance(String word1, String word2) {
        char[] c1 = word1.toCharArray();
        char[] c2 = word2.toCharArray();
        int[][] menu = new int[c1.length][c2.length];
        return helper(c1,c2,c1.length-1,c2.length-1,menu);
    }
    public int helper(char[] c1, char[] c2, int n, int m, int[][] menu){
        if (n == -1) return m+1;
        if (m == -1) return n+1;
        if (menu[n][m] != 0) return menu[n][m];

        if (c1[n] == c2[m]){
            return menu[n][m] = helper(c1,c2,n-1,m-1,menu); //相同不用改变
        }else{ //删除、插入、交换改变最小的那一个
            return menu[n][m] = Math.min(helper(c1,c2,n-1,m,menu)+1,
                            Math.min(helper(c1,c2,n,m-1,menu)+1,
                            helper(c1,c2,n-1,m-1,menu)+1)
                            );
        }
    }

}
// String st = String.valueOf(c);
// char[] c = st.toCharArray();
//类似最长公共子序列
//思路1:暴力递归 找出每个操作中删除、插入、交换改变最小的那一个
//思路2:加个备忘录进行记录

solution3 动态规划(自底向上)

class Solution {
    public int minDistance(String word1, String word2) {
        char[] c1 = word1.toCharArray();
        char[] c2 = word2.toCharArray();
        int[][] dp = new int[c1.length+1][c2.length+1];
        //base case
        for (int i = 0; i < c1.length+1; i++) dp[i][0] = i;
        for (int j = 0; j < c2.length+1; j++) dp[0][j] = j;
        //状态转移
        for (int i = 1; i < c1.length+1; i++){
            for (int j = 1; j < c2.length+1; j++){
                if(c1[i-1] == c2[j-1]) dp[i][j] = dp[i-1][j-1];
                else dp[i][j] = Math.min(dp[i-1][j]+1,Math.min(dp[i][j-1]+1,dp[i-1][j-1]+1));
            }
        }
        return dp[c1.length][c2.length];
    }
}
// String st = String.valueOf(c);
// char[] c = st.toCharArray();
//类似最长公共子序列
//思路1:暴力递归 找出每个操作中删除、插入、交换改变最小的那一个
//思路2:加个备忘录进行记录
//思路3:1.dp数组 2.base case 3.根据实际情况列动态递归方程
posted @ 2020-08-28 15:24  gg12138  阅读(175)  评论(0编辑  收藏  举报