[LeetCode] 718. Maximum Length of Repeated Subarray 最長的重復子數組


 

Given two integer arrays A and B, return the maximum length of an subarray that appears in both arrays.

Example 1:

Input:
A: [1,2,3,2,1]
B: [3,2,1,4,7]
Output: 3
Explanation: 
The repeated subarray with maximum length is [3, 2, 1].

Note:

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

 

這道題給了我們兩個數組A和B,讓返回連個數組的最長重復子數組。那么如果將數組換成字符串,實際這道題就是求 Longest Common Substring 的問題了,而貌似 LeetCode 上並沒有這種明顯的要求最長相同子串的題,注意需要跟最長子序列 Longest Common Subsequence 區分開,關於最長子序列會在 follow up 中討論。好,先來看這道題,既然是子數組,那么重復的地方一定是連續的,而且起點可能會是在數組中的任意地方,這樣的話,最暴力的方法就是遍歷A中的每個位置,把每個位置都當作是起點進行和B從開頭比較,每次A和B都同時前進一個,假如相等,則計數器會累加1,不相等的話,計數器會重置為0,每次用計數器 cnt 的長度來更新結果 res。然后用同樣的方法對B也處理一遍,把每個位置都當作是起點進行和A從開頭比較,每次A和B都同時前進一個,這樣最終下來,就可以求出最長重復子數組的長度,令人驚喜的是,這種暴力搜索解法的擊敗率相當的高,參見代碼如下:

 

解法一:

class Solution {
public:
    int findLength(vector<int>& A, vector<int>& B) {
        int m = A.size(), n = B.size(), res = 0;
        for (int offset = 0; offset < m; ++offset) {
            for (int i = offset, j = 0; i < m && j < n;) {
                int cnt = 0;
                while (i < m && j < n && A[i++] == B[j++]) ++cnt;
                res = max(res, cnt);
            }
        }
        for (int offset = 0; offset < n; ++offset) {
            for (int i = 0, j = offset; i < m && j < n;) {
                int cnt = 0;
                while (i < m && j < n && A[i++] == B[j++]) ++cnt;
                res = max(res, cnt);
            }
        }
        return res;
    }
};

 

我們還可以使用二分法+哈希表來做,別問博主怎么知道(看了題目標簽,然后去論壇上找對應的解法即可,哈哈~)。雖然解法看起來很炫,但不太簡潔,不是博主的 style,但還是收錄進來吧。這里使用二分搜索法來找什么呢?其實是來直接查找最長重疊子數組的長度的,因為這個長度是有范圍限制的,在 [0, min(m, n)] 之間,其中m和n分別是數組A和B的長度。這樣每次折半出一個 mid,然后驗證有沒有這么一個長度為 mid 的子數組在A和B中都存在。從數組中取子數組有些麻煩,可以將數組轉為字符串,取子串就相對來說容易一些了。將數組A和B都先轉化為字符串 strA 和 strB,但是這里很 tricky,轉換的方式不能是直接將整型數字轉為字符串,再連接起來,這樣會出錯,因為會導致一個整型數占據多位字符,所以這里是需要將每個整型數直接加入字符串,從而將該整型數當作 ASCII 碼來處理,尋找對應的字符,使得轉換后的 strA 和 strB 變成各種凌亂的怪異字符,不過不影響解題。這里的二分應該屬於博主之前的總結貼 LeetCode Binary Search Summary 二分搜索法小結 中的第四類,但是寫法上卻跟第三類的變形很像,因為博主平時的習慣是右邊界設置為開區間,所以初始化為 min(m, n)+1,當然博主之前就說過二分搜索的寫有各種各樣的,像這個帖子中寫法也是可以的。博主的這種寫法實際上是在找第一個不大於目標值的數,這里的目標值就是那個 helper 子函數,也就是驗證函數。如何實現這個驗證函數呢,由於是要找長度為 len 的子串是否同時存在於 strA 和 strB 中,可以用一個 HashSet 保存 strA 中所有長度為 len 的子串,然后遍歷 strB 中所有長度為 len 的子串,假如有任何一個在 HashSet 中存在,則直接返回 true,否則循環退出后,返回 false,參見代碼如下:

 

解法二:

class Solution {
public:
    int findLength(vector<int>& A, vector<int>& B) {
        string strA = stringify(A), strB = stringify(B);
        int left = 0, right = min(A.size(), B.size()) + 1;
        while (left < right) {
            int mid = (left + right) / 2;
            if (helper(strA, strB, mid)) left = mid + 1;
            else right = mid;
        }
        return right - 1;
    }
    bool helper(string& strA, string& strB, int len) {
        unordered_set<string> st;
        for (int i = 0, j = len; j <= strA.size(); ++i, ++j) {
            st.insert(strA.substr(i, j - i));
        }
        for (int i = 0, j = len; j <= strB.size(); ++i, ++j) {
            if (st.count(strB.substr(i, j - i))) return true;  
        } 
        return false;
    }
    string stringify(vector<int>& nums) {
        string res;
        for (int num : nums) res += num;
        return res;
    }
};

 

對於這種求極值的問題,動態規划 Dynamic Programming 一直都是一個很好的選擇,這里使用一個二維的 DP 數組,其中 dp[i][j] 表示數組A的前i個數字和數組B的前j個數字的最長子數組的長度,如果 dp[i][j] 不為0,則A中第i個數組和B中第j個數字必須相等,比對於這兩個數組 [1,2,2] 和 [3,1,2],dp 數組為:

 

 3 1 2
1 0 1 0
2 0 0 2
2 0 0 1

 

注意觀察,dp 值不為0的地方,都是當 A[i] == B[j] 的地方,而且還要加上左上方的 dp 值,即 dp[i-1][j-1],所以當前的 dp[i][j] 就等於 dp[i-1][j-1] + 1,而一旦 A[i] != B[j] 時,直接賦值為0,不用多想,因為子數組是要連續的,一旦不匹配了,就不能再增加長度了。每次算出一個 dp 值,都要用來更新結果 res,這樣就能得到最長相同子數組的長度了,參見代碼如下:

 

解法三:

class Solution {
public:
    int findLength(vector<int>& A, vector<int>& B) {
        int res = 0, m = A.size(), n = B.size();
        vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
        for (int i = 1; i <= m; ++i) {
            for (int j = 1; j <= n; ++j) {
                dp[i][j] = (A[i - 1] == B[j - 1]) ? dp[i - 1][j - 1] + 1 : 0;
                res = max(res, dp[i][j]);
            }
        }
        return res;
    }
};

 

Follow up:在開始時,博主提到了要跟最長相同子序列 Longest Common Subsequence 區分開來,雖然 LeetCode 沒有直接求最大相同子序列的題,但有幾道題利用到了求該問題的思想,比如 Delete Operation for Two Strings 和 Minimum ASCII Delete Sum for Two Strings 等,詳細討論請參見評論區一樓 :)

 

Github 同步地址:

https://github.com/grandyang/leetcode/issues/718

 

類似題目:

Minimum Size Subarray Sum

 

參考資料:

https://leetcode.com/problems/maximum-length-of-repeated-subarray/

https://leetcode.com/problems/maximum-length-of-repeated-subarray/discuss/109068/JavaC%2B%2B-Clean-Code-8-lines

https://leetcode.com/problems/maximum-length-of-repeated-subarray/discuss/109039/Concise-Java-DP%3A-Same-idea-of-Longest-Common-Substring

https://leetcode.com/problems/maximum-length-of-repeated-subarray/discuss/109033/Solution-1%3A-DP-O(n2)-with-O(n)-space-Solution-2%3A-Stringify

 

LeetCode All in One 題目講解匯總(持續更新中...)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM