zoukankan      html  css  js  c++  java
  • LeetCode 718. 最长重复子数组 | Python

    718. 最长重复子数组


    题目来源:力扣(LeetCode)https://leetcode-cn.com/problems/maximum-length-of-repeated-subarray

    题目


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

    示例 1:

    输入:
    A: [1,2,3,2,1]
    B: [3,2,1,4,7]
    输出: 3
    解释:
    长度最长的公共子数组是 [3, 2, 1]。
    

    说明:

    • 1 <= len(A), len(B) <= 1000
    • 0 <= A[i], B[i] < 100

    解题思路


    开始之前,先分析下题目。题目中要求计算两个数组的最长公共子数组。从示例中可以看到,子数组要在原数组中连续。那么,我们可以使用暴力的解法尝试逐个比较,示例代码大致如下:

    class Solution:
        def findLength(self, A: List[int], B: List[int]) -> int:
            ans = 0
            for i in range(len(A)):
                for j in range(len(B)):
                    length = 0
                    while i+length < len(A) and j + length < len(B) and A[i+length] == B[j+length]:
                        length+=1
                    ans = max(ans, length)
    
            return ans
    

    大致说下执行的流程,这段代码中,先枚举数组 A 和 数组 B 的起始位置,然后逐个比较元素是否相同计算最长公共前缀长度 length,循环执行直至结束。维护 length,取最大值就是所求答案。

    但是执行这段代码会超时,因为这段代码的时间复杂度最快的情况下是 O(n^3)。但是我们可以根据暴力解的思路进行优化。

    思路:动态规划

    上面已经说明,最坏的情况下,时间复杂度为 O(n^3)。这是因为最坏的情况下,对于任意的 i,j,A[i] 和 B[j] 比较的次数为 min(i+1,j+1)。现在来验证这种情况,假设有以下数组 A 和 数组 B:

    A = [0, 0, 0, 0]
    B = [0, 0, 0, 0]
    

    假设 i,j 都等于 3,那么在暴力解代码中 A[3] 和 B[3] 会被比较 4 次。因为当 (i,j)(0, 0),(1, 1),(2, 2),(3, 3) 的时候,在 while 语句都会判断一次。

    那么优化的思路就从这里进行考虑,使得任意 A[i] 和 B[j] 只需要比较一次。

    也就是说,当确定 A[i] == B[j] 的情况下,A[i:] 和 B[j:] 的公共前缀长度会等于 A[i+1:]B[j+1:] 的公共前缀长度加 1。否则的话,A[i:] 和 B[j:] 的公共前缀长度为 0。(因为要求子数组连续,此时首元素不相等)

    那么我们假设,dp[i][j] 表示 A[i:] 和 B[j:] 的公共前缀长度,根据上面的分析可以得到:

    A[i]==B[j] 时,那么 dp[i][j] = dp[i+1][j+1] + 1,否则 dp[i][j] = 0

    求得所有的 dp[i][j],其中最大的就是要求的答案。

    由于 dp[i][j] 是由 dp[i+1][j+1] 得到的,那么遍历数组的时候,从右往左遍历求解。

    具体的实现代码见【代码实现 # 动态规划】。

    思路:滑动窗口

    这道题还可以使用滑动窗口的方法。在前面的方法可以看到,需要进行多次比较之后才开始计算公共前缀。这是因为重复子数组在两个原数组中的起始位置有可能不一样。

    如果知道起始位置的话,那么从当前位置开始遍历,就可以计算出最长公共的子数组长度。

    那么这里的问题就是如何知道相应的起始位置并对齐。这里分为两种情况:

    • 固定数组 A,移动数组 B,使得 B 的首元素与数组 A 某个元素对齐,找到起始位置,计算长度;
    • 固定数组 B,移动数组 A,使得 A 的首元素与数组 B 某个元素对齐,找到起始位置,计算长度。

    以示例 1 为例:

    输入:
    A: [1,2,3,2,1]
    B: [3,2,1,4,7]
    输出: 3
    解释: 
    长度最长的公共子数组是 [3, 2, 1]。
    

    具体实现的过程如下图:

    图解

    具体实现代码见【代码实现 # 滑动窗口】

    代码实现


    # 动态规划
    class Solution:
        def findLength(self, A: List[int], B: List[int]) -> int:
            A_length = len(A)
            B_length = len(B)
    
            max_length = 0
    
            dp = [[0] * (B_length+1) for _ in range(A_length+1)]
    
            # dp[i][j] 由 dp[i+1][j+1] 转移得到,所以从右往左遍历求解
            for i in range(A_length-1, -1, -1):
                for j in range(B_length-1, -1, -1):
                    if A[i] == B[j]:
                        dp[i][j] = dp[i+1][j+1] + 1
                    else:
                        dp[i][j] = 0
                    
                    max_length = max(max_length, dp[i][j])
            
            return max_length
    
    
    # 滑动窗口
    class Solution:
        def findLength(self, A: List[int], B: List[int]) -> int:
            def get_max_length(a_start, b_start, length):
                max_length = 0
                count = 0
                # 计算这个区域,最长公共子串长度
                for i in range(length):
                    if A[a_start+i] == B[b_start+i]:
                        count+=1
                        max_length = max(max_length, count)
                    else:
                        count = 0
                
                return max_length
            
            A_length = len(A)
            B_length = len(B)
    
            ans = 0
    
            # 固定 A,移动 B,使得 B 首元素对应 A 某个元素
            for i in range(A_length):
                length = min(A_length-i, B_length)
                ans = max(ans, get_max_length(i, 0, length))
            
            # 固定 B,移动 A,使得 A 首元素对应 B 某个元素
            for j in range(B_length):
                length = min(A_length, B_length-j)
                ans = max(ans, get_max_length(0, j, length))
            
            return ans
    

    实现结果


    实现结果 | 动态规划

    实现结果 | 动态规划

    实现结果 | 滑动窗口

    实现结果 | 滑动窗口

    总结


    • 题目要求,两个数组的最长公共子数组,那么我们可以考虑先使用暴力解法来尝试解决问题。枚举数组 A 和数组 B 的每个元素作为起始位置逐个元素是否相同,计算最长公共前缀长度 length,循环直至结束。维护更新 length,取最大值。(执行结果超时
    • 虽然暴力解法执行会超时,但是可以看出其中不足的地方。因为最坏的情况下,对于任意 i、j,A[i]B[j] 比较的次数为 min(i+1,j+1)。可以考虑从这个方向去优化算法,这里考虑使用动态规划。
      • 当我们确定 A[i] == B[j] 的情况下,此时的公共前缀长度为 1,A[i:] 和 B[j:] 的公共前缀长度会等于 A[i+1:] 和 B[j+1:] 的公共前缀长度加上当前这个公共前缀长度 1,否则的话,A[i:] 和 B[j:] 的公共前缀长度为 0。
      • 这里设 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
    • 本题还可以使用滑动窗口的方法求解。前面的算法中,可以看到都需要先逐个比较,计算长度。这是因为两个数组重复部分起始位置不同。也就是说,当确定起始位置,从当前位置遍历也可以求得结果。主要的难题在于如果对齐两个数组,具体分为如下情况:
      • 先固定 A,移动 B,使得 B 的首元素与数组 A 某个元素对齐;
      • 或者固定 B,移动 A,使得 A 的首元素与数组 B 某个元素对齐。

    文章原创,欢迎关注点赞。微信公众号《书所集录》同步更新,同样欢迎关注。

    qrcode_for_Demon

  • 相关阅读:
    Delphi TcxComboBox控件说明
    Delphi ComboBox的属性和事件、及几个鼠标事件的触发
    Delphi XE5的新功能“ TListView内置搜索过滤”
    可能是全网最全的解决摄像头无法创建视频捕捉过滤器问题?
    Delphi – TDataSet确定它是否在插入/编辑状态时被修改
    Delphi 获取DataSet传入参数后的SQL命令
    TClientDataSet[7]: 辨析 Field、FieldDef、Fields、FieldDefs、FieldList、FieldDefList
    枚举HasFlag函数实现的内部逻辑是什么
    在DBGrid中用代码实现按回车键跳到下一格的方法
    eclipse下没有Dynamic Web Project的处理方法
  • 原文地址:https://www.cnblogs.com/yiluolion/p/13221352.html
Copyright © 2011-2022 走看看