动态规划(3)

94 阅读2分钟

Day07 公共子串、子数组问题

1、最长重复子数组

  • 思路:
    • DFS直接进行暴力回溯搜索.超时

    注意区别月最长公共子串的DFS。在数组不是公共数组时,要及时停止DFS搜索

    class Solution {
        public int findLength(int[] nums1, int[] nums2) {
            int res = 0;
            for (int i = 0; i < nums1.length; i++) {
                for (int j = 0; j < nums2.length; j++) {
                    res = Math.max(dfs(nums1, nums2, i, j), res);
                }
            }
            return res;
        }
        //递归思路不同于最长公共子序列。子数组在不公共时停止深入搜索
        private int dfs(int[] num1, int[] num2, int index1, int index2) {
            if (index1 >= num1.length || index2 >= num2.length) {
                return 0;
            }
            if (num1[index1] != num2[index2]) return 0;
            else return dfs(num1, num2, index1 + 1, index2 + 1) + 1;
        }
    }
    
    • DP打表优化
    class Solution {
        int[][] dp;
    
        public int findLength(int[] nums1, int[] nums2) {
            int res = 0;
            int m = nums1.length;
            int n = nums2.length;
            dp = new int[m][n];
            for (int i = 0; i < m; i++) {
                for (int j = 0; j < n; j++) {
                    res = Math.max(dfs(nums1, nums2, i, j), res);
                }
            }
            return res;
        }
    
        //递归思路不同于最长公共子序列。子数组在不公共时停止深入搜索
        private int dfs(int[] num1, int[] num2, int index1, int index2) {
            if (index1 >= num1.length || index2 >= num2.length) {
                return 0;
            }
            if (num1[index1] != num2[index2]) return 0;
            else {
                if (dp[index1][index2] != 0) return dp[index1][index2];
                dp[index1][index2] = dfs(num1, num2, index1 + 1, index2 + 1) + 1;
            }
            return dp[index1][index2];
        }
    }
    
    • 有公共子问题,思考如何使用动态规划的思路,从子问题出发解决问题
    class Solution {
        public int findLength(int[] nums1, int[] nums2) {
            int ans = 0;
            int m = nums1.length;
            int n = nums2.length;
            //创建DP数组,dp[i][j]表示以i-1、j-1结尾的公共子数组长度。
            int[][] dp = new int[m + 1][n + 1];
            //初始化,当i、j分别取1时
            for (int i = 1; i <= m; i++) {
                for (int j = 1; j <= n; j++) {
                    if (nums1[i - 1] == nums2[j - 1]) {
                        dp[i][j] = dp[i - 1][j - 1] + 1;
                    }
                    ans = Math.max(dp[i][j], ans);
                }
            }
            return ans;
        }
    }
    
    • 空间复杂度优化
      • 画图分析,dp[i][j]只与dp[i-1][j-1]有关,直接压缩成dp[j]
    class Solution {
        public int findLength(int[] nums1, int[] nums2) {
            int ans = 0;
            int n = nums2.length;
            //创建DP数组,dp[i][j]表示以i-1、j-1结尾的公共子数组长度。
            int[] dp = new int[n + 1];
            //初始化,当i、j分别取1时
            for (int i = 1; i <= nums1.length; i++) {
                //滚动数组,下一次循环用到上一次循环构建的数组
                for (int j = n; j > 0; j--) {
                    if (nums2[j - 1] == nums1[i - 1]){
                        dp[j] = dp[j - 1] + 1;
                    }else {
                        //必须进行滚动覆盖!!!
                        dp[j] = 0;
                    }
                    ans = Math.max(dp[j],ans);
                }
            }
            return ans;
        }
    }
    

2、最长公共子序列

  • 思路
    • 暴力回溯+打表优化
    • 注意跟题1的DFS做区分。区别在不匹配是否还向后搜索
    • 子序列不用连续,不匹配需要继续向后搜索
    class Solution {
        int[][] dp;
    
        public int longestCommonSubsequence(String text1, String text2) {
            dp = new int[text1.length()][text2.length()];
            for (int i = 0; i < text1.length(); i++) {
                for (int j = 0; j < text2.length(); j++) {
                    dp[i][j] = -1;
                }
            }
            return dfs(text1, text2, 0, 0);
        }
    
        private int dfs(String text1, String text2, int index1, int index2) {
            if (index1 >= text1.length() || index2 >= text2.length()) {
                return 0;
            }
            if (dp[index1][index2] != -1) return dp[index1][index2];
            if (text1.charAt(index1) == text2.charAt(index2)) {
                dp[index1][index2] = dfs(text1, text2, index1 + 1, index2 + 1) + 1;
            } else {
                dp[index1][index2] = Math.max(dfs(text1, text2, index1 + 1, index2 + 1)
                        , Math.max(dfs(text1, text2, index1 + 1, index2)
                                , dfs(text1, text2, index1, index2 + 1)));
            }
            return dp[index1][index2];
        }
    }
    
    • 有重叠子问题,试试动态规划。
    class Solution {
        public int longestCommonSubsequence(String text1, String text2) {
            int n = text1.length();
            int m = text2.length();
            //DP 数组表示前i、j个字符的最长公共子序列
            // i、j位置字符相同,则结果为dp[i-1][j-1]+1
            //否则  应该是 dp[i-1][j] dp[i][j-1]的最大值
            int[][] dp = new int[n + 1][m + 1];
            for (int i = 1; i <= n; i++) {
                for (int j = 1; j <= m; 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[n][m];
        }
    
    }