LeetCode 718. 最长重复子数组

题目描述

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

动态规划

两个数组,找公共子数组。第一思路是动态规划,设f[i,j]表示在nums1中以位置i结尾,nums2中以位置j结尾,的全部公共子数组中,长度最大的公共子数组的长度。

  • nums1[i] != nums2[j],很明显不存在公共子数组,故f[i,j] = 0
  • nums1[i] == nums2[j],存在公共子数组,f[i,j] = f[i - 1, j - 1] + 1
class Solution {
    public int findLength(int[] nums1, int[] nums2) {
        int n = nums1.length, m = nums2.length;
        int[][] f = new int[n + 1][m + 1];
        int ans = 0;
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                if (nums1[i - 1] == nums2[j - 1]) {
                    f[i][j] = f[i - 1][j - 1] + 1;
                    ans = Math.max(ans, f[i][j]);
                }
            }
        }
        return ans;
    }
}

优化:观察可以知道f[i][j]的状态,只取决于其左上角的状态(上一行的左侧那个位置),则可以用滚动数组的思想,优化为一维。(每一行状态的更新,从右往左)

class Solution {
    public int findLength(int[] nums1, int[] nums2) {
        int n = nums1.length, m = nums2.length;
        int[] f = new int[m + 1];
        int ans = 0;
        for (int i = 1; i <= n; i++) {
            for (int j = m; j >= 1; j--) {
                if (nums1[i - 1] == nums2[j - 1]) {
                    f[j] = f[j - 1] + 1;
                    ans = Math.max(ans, f[j]);
                } else {
                    f[j] = 0; // 需要还原为0
                }
            }
        }
        return ans;
    }
}

动规的时间复杂度是 O ( n × m ) O(n × m) O(n×m),空间复杂度是 O ( m i n ( n , m ) ) O(min(n,m)) O(min(n,m))

滑动窗口

最长重复子数组,一定是将两个数组在某个位置上对齐,之后在重合的区间内,能够得到的最长的子数组。枚举所有的对齐方式,然后判断此时重合区间内的重复子数组的长度即可。

class Solution {
    public int findLength(int[] nums1, int[] nums2) {
        int n = nums1.length, m = nums2.length;
        int ans = 0;
        for (int i = m - 1; i >= 0; i--) {
            ans = Math.max(ans, getMaxLen(nums1, 0, nums2, i));
        }

        for (int i = 1; i < n; i++) {
            ans = Math.max(ans, getMaxLen(nums1, i, nums2, 0));
        }

        return ans;
    }

    private int getMaxLen(int[] nums1, int i, int[] nums2, int j) {
        int n = nums1.length, m = nums2.length;
        int len = 0, maxLen = 0;
        while (i < n && j < m) {
            if (nums1[i] == nums2[j]) {
                len++;
            } else {
                maxLen = Math.max(maxLen, len); // 断掉了, 记录答案
                len = 0; // 重置
            }
            i++;
            j++;
        }
        // 最后一次的len
        maxLen = Math.max(maxLen, len);
        return maxLen;
    }
}

时间复杂度 O ( ( n + m ) × m i n ( n , m ) ) O((n + m) × min(n,m)) O((n+m)×min(n,m)),空间复杂度 O ( 1 ) O(1) O(1)

二分 + 哈希

由于,当存在一个长度为k的公共子数组时,那么一定会存在一个长度小于k的子数组,那么我们可以使用二分,每次二分检查一下,两个数组中是否存在长度为len的重复子数组,如果存在,则往右侧查找,若不存在,则往左侧查找。

如何快速判断两个数组中是否存在某个长度的重复子数组呢?使用类似字符串哈希的方式 -> 参考这篇文章

class Solution {
    final int P = 131;
    int[] hashA, hashB;
    int[] p; // 存P的幂
    public int findLength(int[] nums1, int[] nums2) {
        int n = nums1.length, m = nums2.length;
        hashA = new int[n + 1];
        hashB = new int[m + 1];
        p = new int[Math.max(n, m) + 1];

        // 预处理出所有的哈希值
        p[0] = 1; // P^0 = 1
        for (int i = 1; i < p.length; i++) {
            p[i] = p[i - 1] * P;
        }

        for (int i = 1; i <= n; i++) {
            hashA[i] = hashA[i - 1] * P + nums1[i - 1];
        }

        for (int i = 1; i <= m; i++) {
            hashB[i] = hashB[i - 1] * P + nums2[i - 1];
        }

        // 开始二分
        int l = 0, r = Math.min(n, m);
        while (l < r) {
            int mid = l + r + 1 >> 1; // 查看一下这个长度是否存在公共子数组
            if (check(mid)) l = mid; // 若这个长度存在, 则答案在右侧
            else r = mid - 1;
        }
        return l;
    }

    // 检查@len 这个长度, 是否存在公共子数组
    private boolean check(int len) {
        Set<Integer> set = new HashSet<>();
        int n = hashA.length - 1;
        int m = hashB.length - 1;
        for (int i = 1; i + len - 1 <= n; i++) {
            int h = hashA[i + len - 1] - hashA[i - 1] * p[len];
            set.add(h);
        }

        for (int i = 1; i + len - 1 <= m; i++) {
            int h = hashB[i + len - 1] - hashB[i - 1] * p[len];
            if (set.contains(h)) return true;
        }
        return false;
    }
}

时间复杂度: O ( ( n + m ) × l o g ( m i n ( m , n ) ) ) O((n + m) × log(min(m,n))) O((n+m)×log(min(m,n)))

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值