LeetCode 718. 最长重复子数组/最长公共子串(C++)

题目地址:力扣

题目难度:Medium

涉及知识点:动态规划、滑动窗口


分析:若把题目中的数组换成字符串,那么这道题就变成了最长公共子串问题。与最长公共子序列不同,子串要必须连续。

解法1:暴力求解

思路:先固定第一个数组的值遍历第二个数组,若发现比较的两个元素相等,则使用临时变量来一起往后看,看最长多少位相等,更新全局最大解;再继续遍历第一个数组的值,直到遍历不可能再出现更长的为止。

class Solution {
public:
    int findLength(vector<int>& nums1, vector<int>& nums2) {
        // 定义数组大小以及全局最大解
        int sz1 = nums1.size(), sz2 = nums2.size();
        int max_len = 0;
        // 遍历数组1,当发现后面剩下的元素不足以超过最大值的时候就跳出循环
        for (int i = 0; i < sz1-max_len; ++i)
        {
            // 遍历数组2
            for (int j = 0; j < sz2; ++j)
            {
                // 若当前元素相等
                if (nums1[i] == nums2[j]) {
                    int cur_len = 0;
                    int tmp1 = i, tmp2 = j;
                    // 则一起往后看最高有多少位相等
                    while (tmp1 != sz1 && tmp2 != sz2 && nums1[tmp1] == nums2[tmp2])
                    {
                        ++cur_len, ++tmp1, ++tmp2;
                    }
                    // 更新全局最大解
                    max_len = max(max_len, cur_len);
                }
            }
        }
        return max_len;
    }
};

Accepted

  • 57/57 cases passed (1000 ms)
  • Your runtime beats 5.08 % of cpp submissions
  • Your memory usage beats 92.06 % of cpp submissions (11.4 MB)

解法2:滑动窗口

思路:在暴力解法中,我们开始计算公共子串的长度的步骤总是等到两个数组的当前元素相等的时候才开始,但是两个元素相等的位置我们根本不知道在哪,所以任何地方都有可能,即第一个数组的每个元素都有可能和第二个数组的任意位置元素开始匹配。因此我们可以遍历这种假设,即采用滑动窗口的方法。这里借用力扣大佬的图(天高L)来描述这个过程。我们可以看到,经过这样的滑动窗口之后,数组1的每个数都和数组2的每一个数对齐进行了一次匹配比较,因此只需要记录下匹配过程中的最大子串就完成了整个过程。

对于两个字符串,我们发现固定较短的串,使用较长的串滑动实现起来较为容易。

同时,滑动窗口可以分为三个阶段。

1、移入阶段,未完全重合。窗口从1开始增加,直到窗口尺寸等于固定的串长度

2、完全重合阶段。窗口尺寸等于固定的串长度,继续向右滑动

3、移出阶段,未完全重合。窗口尺寸小于固定的串长度,直至减小为1

class Solution {
public:
    // 已经固定窗口,需要寻找两个字符串的最大相同部分
    int findMax(vector<int>& a_fix, vector<int>& a_sli, int start, int end)
    {
        // idx_fix表示固定串开始遍历的位置,res表示子串最大值,cur_len表示当前串长度
        int idx_fix = 0;
        int res = 0;
        int cur_len = 0;
        // 若发现左指针已经到滑动串的最左边,说明已经进入了重合阶段
        // 根据窗口大小,调整固定串开始遍历的位置
        if (start == 0) {
            idx_fix = a_fix.size() - (end - start + 1);
        }
        // 遍历整个窗口
        while(start <= end)
        {
            // 若两个串当前位置相等则当前串长度加1
            if (a_fix[idx_fix++] == a_sli[start++]) {
                ++cur_len;
            } else {
                // 若不等且之前有相同串,那么更新最大值。然后重新计算长度
                if (cur_len > 0)
                    res = max(res, cur_len);
                cur_len = 0;
            }
        }
        // 因为循环可能存在直到最后一个元素都相等的情况,所以需要手动更新一次最大值,
        res = max(res, cur_len);
        return res;
    }

    // 滑动窗口过程,a_fix表示固定的串,a_sli表示用于滑动的串,后面的参数分别为其长度
    int slide(vector<int>& a_fix, vector<int>& a_sli, int sz_fix, int sz_sli)
    {
        // lp为左指针,rp为右指针,初始时都指向滑动串的最后元素。初始化最大子串长度为0
        int lp = sz_sli-1, rp = sz_sli-1;
        int max_len = 0;
        // rp - lp + 1 计算的就是当前窗口的尺寸
        // 从左移入阶段,未完全重合,即窗口的尺寸小于固定串的长度
        while (rp - lp + 1 < sz_fix)
        {
            max_len = max(max_len, findMax(a_fix, a_sli, lp, rp));
            // 移入阶段是慢慢向左展开,因此左指针自减
            --lp;
        }
        // 完全重合阶段,同时左指针不为0
        while (rp - lp + 1 == sz_fix && lp != 0)
        {
            max_len = max(max_len, findMax(a_fix, a_sli, lp, rp));
            // 重合阶段,左指针和右指针同时往左移动
            --lp, --rp;
        }
        // 向右移出阶段,从重合变为未完全重合
        while (rp != lp)
        {
            max_len = max(max_len, findMax(a_fix, a_sli, lp, rp));
            // 移出阶段是慢慢向左收拢,因此右指针自减
            --rp;
        }
        return max_len;
    }

    int findLength(vector<int>& nums1, vector<int>& nums2) {
        // 定义两个数组大小,初始化最大子串长度为0
        int sz1 = nums1.size(), sz2 = nums2.size();
        int max_len = 0;
        // 根据数组长度选出较短的和较长的
        vector<int>& a_short = sz1 > sz2 ? nums2 : nums1;
        vector<int>& a_long = (a_short == nums1) ? nums2 : nums1;
        // 滑动窗口求得最大子串长度
        max_len = slide(a_short, a_long, a_short.size(), a_long.size());
        // 返回结果
        return max_len;
    }
};

Accepted

  • 57/57 cases passed (124 ms)
  • Your runtime beats 95.76 % of cpp submissions
  • Your memory usage beats 99.68 % of cpp submissions (11.2 MB)

解法3:动态规划

思路:这道题使用dp的思路和最长子序列的思路十分类似。唯一的不同就在于,最长子序列允许序列并非连续的,而最长子数组(子串)要求序列是连续的。这也就意味着我们dp保存的应该是两个数组分别从最后开始往前找,最长的子串长度。比如判断“123”和“321”子串在dp数组中保存的值应该为0,因为这两个子串的最后一位不相等。而“12”和“32”子串在dp数组中保存的值就为1,因此它们最后一位相等,然后再往前找,发现不相等,于是最长就为1。这也就意味着我们最长子串的结果并非在右下角,而是可能在dp数组中的任意位置。那么我们就需要在路径中动态更新全局最大值。

class Solution {
public:
    int findLength(vector<int>& nums1, vector<int>& nums2) {
        // 记录两个子串长度,全局最大长度以及dp数组
        int sz1 = nums1.size(), sz2 = nums2.size();
        int max_len = 0;
        // 这里开辟的两个维度的大小均为数组长度大一位,避免特判
        vector<vector<int>> dp (sz1 + 1, vector<int> (sz2 + 1));
        // 循环第一个数组
        for (int i = 0; i < sz1; ++i)
        {
            // 循环第二个数组
            for (int j = 0; j < sz2; ++j)
            {
                // 若当前元素相等,其最大长度等于左对角线上方的一个元素的值加1
                if (nums1[i] == nums2[j]) {
                    dp[i+1][j+1] = dp[i][j] + 1;
                    // 更新全局最大值
                    max_len = max_len > dp[i+1][j+1] ? max_len : dp[i+1][j+1];
                } else {
                    // 若当前元素不等直接赋0
                    dp[i+1][j+1] = 0;
                }
            }
        }
        return max_len;
    }
};

注意:子数组1中位置为i的子串与子数组2中位置为j的子串,其最大长度存在dp数组的位置在dp[i+1][j+1]

Accepted

  • 53/53 cases passed (236 ms)
  • Your runtime beats 77.49 % of cpp submissions
  • Your memory usage beats 75.83 % of cpp submissions (83.4 MB)
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值