leetcode-718. 最长重复子数组

leetcode-718. 最长重复子数组
给两个整数数组 A 和 B ,返回两个数组中公共的、长度最长的子数组的长度。

示例:
输入:
A: [1,2,3,2,1]
B: [3,2,1,4,7]
输出:3

解释:
长度最长的公共子数组是 [3, 2, 1] 。

动态规划

class Solution {
public:
    int findLength(vector<int>& A, vector<int>& B) {
        int lenA = A.size(), lenB = B.size();
        vector<vector<int>> dp(lenA + 1, vector<int>(lenB + 1, 0));
        int ret = 0;
		// 从后往前,dp[lenA][x] = 0 dp[x][lenB] = 0,初始状态在最后一行和最后一列
        for (int i = lenA - 1; i >= 0; i--) {
            for (int j = lenB - 1; j >= 0; j--) {
                dp[i][j] = (A[i] == B[j] ? dp[i + 1][j + 1] + 1 : 0);
                ret = max(ret, dp[i][j]);
            }
        }
        return ret;
    }
};

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

【我自己实现的】

class Solution {
public:
    int findLength(vector<int>& nums1, vector<int>& nums2) {
		int len1 = nums1.size();
		int len2 = nums2.size();
		int ret = 0;
		vector<vector<int>> dp(len1 + 1, vector<int>(len2 + 1, 0));
		for(int i = 1; i <= len1; i++) {
			for(int j = 1; j <= len2; j++) {
				if(nums1[i-1] == nums2[j-1]) {
					dp[i][j] =  dp[i - 1][j - 1] + 1;
					ret = max(ret, dp[i][j]);
				} else {
					dp[i][j] = 0;
				}
			}
		}
		
		return ret;
    }
};

【我自己的优化】

class Solution {
public:
    int findLength(vector<int>& nums1, vector<int>& nums2) {
		int len1 = nums1.size();
		int len2 = nums2.size();
		int ret = 0;
		vector<int> dp(len2 + 1, 0);
		for(int i = 1; i <= len1; i++) {
			// 二维的时候不管遍历顺序怎么变,都能通过 dp[i][j] 找到 dp[i - 1][j - 1]
			// 一维的时候,j 从后往前遍历时,对于当前次的dp[j] (即dp[i][j]),
			// 才能找到前一次for循环的dp[j-1],(即dp[i-1][j-1])
			for(int j = len2; j > 0; j--) {
				if(nums1[i-1] == nums2[j-1]) {
					dp[j] =  dp[j - 1] + 1;
					ret = max(ret, dp[j]);
				} else {
					dp[j] = 0;
				}
			}
		}
		
		return ret;
    }
};

滑窗

class Solution {
public:
	// A 从 addA 开始,B 从 addB 开始时,查找长度为 len 的两个数对的最长连续数组
    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;
		// B的头不变,A的头往前走
        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);
        }
		
		// A的头不变,B的头往前走
        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)

N 表示数组 A 的长度,M 表示数组 B 的长度。

字符串哈希

class Solution {
public:
    const int mod = 1e9+7;  // 1 <= nums1.length, nums2.length <= 1000
    const int base = 113; // 0 <= nums1[i], nums2[i] <= 100,涵盖所有元素的质数即可
    
    // 使用快速幂计算 x^n % mod 的值
    long long qPow(long long x, long long n) {
        long long ret = 1;
        while (n) {
            if (n & 1) {
                ret = ret * x % mod;
            }
            x = x * x % mod;
            n >>= 1;
        }
        return ret;
    }

    bool check(vector<int>& A, vector<int>& B, int len) {
        long long hashA = 0;
        for (int i = 0; i < len; i++) {
            hashA = (hashA * base + A[i]) % mod;
        }
        unordered_set<long long> bucketA;
        bucketA.insert(hashA);
        long long mult = qPow(base, len);
        for (int i = len; i < A.size(); i++) {
        	// 此处推动见最下部分
			hashA = ((base*hashA)%mod - (A[i - len]*mult)%mod + A[i]%mod + mod)%mod;
            // hashA = ((hashA - A[i - len] * mult % mod + mod) % mod * base + A[i]) % mod;
            bucketA.insert(hashA);
        }
        long long hashB = 0;
        for (int i = 0; i < len; i++) {
            hashB = (hashB * base + B[i]) % mod;
        }
        if (bucketA.count(hashB)) {
            return true;
        }
        for (int i = len; i < B.size(); i++) {
			hashB = ((base*hashB)%mod - (B[i - len]*mult)%mod + B[i]%mod + mod)%mod;
            // hashB = ((hashB - B[i - len] * mult % mod + mod) % mod * base + B[i]) % mod;
            if (bucketA.count(hashB)) {
                return true;
            }
        }
        return false;
    }

    int findLength(vector<int>& A, vector<int>& B) {
        int left = 1, right = min(A.size(), B.size());
        while (left <= right) {
            int mid = (left + right) >> 1;
            // 不断检查 A 和 B 中长度为 mid 的子串是否相同
            if (check(A, B, mid)) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return left - 1;
    }
};

二分查找的每一步中,使用哈希表分别存储这两个数组的所有长度为 len 的子数组的哈希值,
将它们的哈希值进行比对,如果两序列存在相同的哈希值,则认为两序列存在相同的子数组。
防止哈希碰撞,可以在发现两个子数组的哈希值相等时,校验它们本身是否相同。
或者采用双hash。

A串的0~len-1个子串的 hash
hash(A[0,len)) = {A[0]*base^(len-1) + A[1]*base^(len-2) + … + A[len-1]*base^0}%mod

A串的1~len个子串的 hash
hash(A[1,len+1)) = {A[1]*base^(len-1) + A[2]*base^(len-2) + … + A[len]*base^0}%mod

如何从 hash(A[0,len)) 推导出 hash(A[1,len+1))
hash(A[1,len+1)) - {A[len]*base^0}%mod
= {A[1]*base^(len-1) + A[2]*base^(len-2) + … + A[len-1]base^1}%mod
= {base
(A[1]*base^(len-2) + A[2]*base^(len-3) + … + A[len-1]base^0)}%mod
= {base
(A[0]*base^(len-1) + A[1]*base^(len-2) + A[2]*base^(len-3) + … + A[len-1]*base^0 - A[0]base^(len-1))}%mod
= {base
(A[0]*base^(len-1) + A[1]*base^(len-2) + A[2]*base^(len-3) + … + A[len-1]*base^0)}%mod - {A[0]base^len}%mod
= {base
hash(A[0,len))}%mod - {A[0]base^len}%mod
∴ hash(A[1,len+1))
= {base
hash(A[0,len))}%mod - {A[0]*base^len}%mod + {A[len]base^0}%mod
= {base
hash(A[0,len))}%mod - {A[0]*base^len}%mod + A[len]%mod

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值