编程题-最长重复子数组(中等)

题目:

给两个整数数组 nums1 和 nums2 ,返回 两个数组中 公共的 、长度最长的子数组的长度 

解法一(动态规划):

不妨设A数组为[1, 2, 3],B两数组为[1, 2, 4],我们比较计算A[0:]B[0:] 最长公共前缀、 A[1:]B[1:] 最长公共前缀以及 A[2:]B[2:] 最长公共前缀时产生的。我们希望优化这一过程,使得任意一对 A[i] 和 B[j] 都只被比较一次。这样我们自然而然想到利用这一次的比较结果。如果 A[ i ] == B[ j ],那么我们知道 A[ i: ] 与 B[ j: ] 的最长公共前缀为 A[ i + 1: ] 与 B[ j + 1: ] 的最长公共前缀的长度加一,否则我们知道 A[ i: ] 与 B[ j: ] 的最长公共前缀为零。

这样我们就可以提出动态规划的解法:令 dp[ i ][ j ] 表示 A[ i: ] 和 B[ j: ] 的最长公共前缀,那么答案即为所有 dp[ i ][ j ] 中的最大值。如果 A[ i ] == B[ j ],那么 dp[ i ][ j ] = dp[ i + 1][ j + 1] + 1,否则 dp[ i ][ j ] = 0。考虑到这里 dp[ i ][ j ] 的值从 dp[ i + 1][ j + 1] 转移得到,所以我们需要倒过来,首先计算 dp[ len(A) - 1][ len(B) - 1],最后计算 dp[ 0 ][ 0 ]。如下为实现代码:

class Solution {
public:
    int findLength(vector<int>& A, vector<int>& B) {
        int n = A.size(), m = B.size();
        vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));
        int ans = 0;
        //倒序访问,遍历所有的dp[i,j]的值,计算最大的dp[i,j]值即为最终的结果
        //如果A[i]==B[j],则dp[i][j]=dp[i+1][j+1]+1,动态规划转移的思想
        for (int i = n - 1; i >= 0; i--) {
            for (int j = m - 1; j >= 0; j--) {
                dp[i][j] = A[i] == B[j] ? dp[i + 1][j + 1] + 1 : 0;
                ans = max(ans, dp[i][j]);
            }
        }
        return ans;
    }
};

 时间复杂度: O(N×M)。空间复杂度: O(N×M)。

解法二(滑动窗口):

我们注意到之所以两位置会比较多次,是因为重复子数组在两个位置中的位置可能不同。以 A = [3, 6, 1, 2, 4]B = [7, 1, 2, 9] 为例,它们的最长重复子数组是 [1, 2],在 AB 中的开始位置不同。

如果知道了开始的位置,我们就可以根据它们将A和B进行【对齐】,此时,最长重复子数组在A和B中的开始位置相同,我们就可以对两个数组进行一次遍历,得到子数组的长度。

我们可以枚举A和B所有的对齐方式。对齐的方式有两类:第一类为A不变,B的首元素与A中的某个元素对齐;第二类为B不变,A的首元素与B中的某个元素对齐。对于每一种对齐方式,我们计算它们相对位置相同的重复子数组即可。

class Solution {
public:
    int maxLength(vector<int>& A, vector<int>& B, int addA, int addB, int len) {
        int ret = 0, k = 0;
        for (int i = 0; i < len; i++) {
            if (A[addA + i] == B[addB + i]) {
                k++;
            } else {
                k = 0;
            }
            ret = max(ret, k);
        }
        return ret;
    }
    int findLength(vector<int>& A, vector<int>& B) {
        int n = A.size(), m = B.size();
        int ret = 0;
        for (int i = 0; i < n; i++) {
            int len = min(m, n - i);
            int maxlen = maxLength(A, B, i, 0, len);
            ret = max(ret, maxlen);
        }
        for (int i = 0; i < m; i++) {
            int len = min(n, m - i);
            int maxlen = maxLength(A, B, 0, i, len);
            ret = max(ret, maxlen);
        }
        return ret;
    }
};

 时间复杂度:O(( N + M )×min( N , M ))。空间复杂度:O(1)。

笔者小记:

1、暴力枚举方法的时间复杂度为O(n^3)过长,可以通过使用动态规划思想或滑动窗口思想并结合合适的数组结构,以降低代码的时间复杂度,目的是尽可能减少数组元素的重复遍历访问情况

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值