718. 最长重复子数组

215 阅读2分钟

思路:动态规划

  • 定义dp[i][j]表示以A[i - 1]B[j - 1]为结尾的公共子数组的最长长度
  • A[i]!=B[j]时,dp[i][j]=0, 因为以A[i]和B[j]结尾的公共子数组不存在,因为它们的末元素不等,相当于把两个个连续的相同的数组截断了
  • A[i]==B[j]时,dp[i][j]=dp[i-1][j-1]+1, 因为A[i]和B[j]相等,以它们俩为结尾的最长公共子数组的长度就是以A[i-1]B[j-1]结尾的最长公共子数组的长度加1

之所以这样定义状态是因为原问题所要求的最长公共子数组如果存在,它一定是以某一个A的元素结尾,也以某一个B的元素结尾,现在就是遍历所有可能的分别以哪两个元素结尾的情况,取其中的最长的情况即可

  • 类比1143.最长公共子序列问题:因为该问题中是子数组,元素要求是连续的,所以当A[i]!=B[j]时,直接置dp[i][j]=0即可,此时以A[i]和B[j]结尾的最长子数组压根不存在,而最长公共子序列问题中,即使A[i]!=B[j],也要将前面求出来的最长子序列长度继承下来。

  • 因此,1143中,右下角的数就是max,这个题的max不一定。

6A72BC228C8D23E061861521B0E181E2.png

  • 为了方便,也多填充一行零
class Solution {
    public int findLength(int[] A, int[] B) {
        int m = A.length;
        int n = B.length;
        int max = 0;
        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 (A[i - 1] == B[j - 1]) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                    max = Math.max(max, dp[i][j]);
                } //else {
                    //dp[i][j] = 0;//可省略,数组新建后默认填充0
               // }
            }
        }
        return max;
    }
}

输出最重复长子数组

class Solution {
    public int findLength(int[] nums1, int[] nums2) {
        int l1 = nums1.length, l2 = nums2.length;
        int[][] dp = new int[l1 + 1][l2 + 1];//多填充一行零
        int max = 0;
        int maxI = 0, maxJ = 0;
        for (int i = 1; i <= l1; i++) {
            for (int j = 1; j <= l2; j++) {
                if (nums1[i - 1] == nums2[j - 1]) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                    // max = Math.max(max, dp[i][j]);
                    if (dp[i][j] > max) {
                        max = dp[i][j];
                        maxI = i;// 记录最长时的坐标
                        maxJ = j;
                    }
                } else {
                    dp[i][j] = 0;
                }
            }
        }
        System.out.println(getPath(nums1, nums2, dp, maxI, maxJ));
        return max;
    }

    // 找到那个最长子数组, 只需从最大的开始,往左上角回退,直到dp=0
    public String getPath(int[] nums1, int[] nums2, int[][] dp, int maxI, int maxJ) {
        StringBuffer sb = new StringBuffer();
        int i = maxI, j = maxJ;
        while (dp[i][j] != 0) {
            sb.append(nums1[i - 1]);
            i--; 
            j--;
        }
        return sb.reverse().toString();
    }
}