你的衣服我扒了 - 《最長公共子序列》

以前出了一篇穿上衣服我就不認識你了?來聊聊最長上升子序列,收到了你們的一致好評。今天給你們帶來的依然是換皮題 - 最長公共子序列系列。git

最長公共子序列是一個很經典的算法題。有的會直接讓你求最長上升子序列,有的則會換個說法,但最終考察的仍是最長公共子序列。那麼問題來了,它穿上衣服你還看得出來是麼?github

若是你徹底看不出來了,說明抽象思惟還不到火候。常常看個人題解的同窗應該會知道,我常常強調抽象思惟。沒有抽象思惟,全部的題目對你來講都是新題。你沒法將以前作題的經驗遷移到這道題,那你作的題意義何在?算法

雖然抽象思惟很難練成,可是幸虧算法套路是有限的,常常考察的題型更是有限的。從這些入手,或許可讓你輕鬆一些。本文就從一個經典到不行的題型《最長公共子序列》,來幫你進一步理解抽象思惟數組

注意。 本文是幫助你識別套路,從橫向上理清解題的思惟框架,並無採用最優解,全部的題目給的解法可能不是最優的,可是均可以經過全部的測試用例。若是你想看最優解,能夠直接去討論區看。或者期待個人 深刻剖析系列

718. 最長重複子數組

題目地址

https://leetcode-cn.com/probl...數據結構

題目描述

給兩個整數數組  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

前置知識

  • 哈希表
  • 數組
  • 二分查找
  • 動態規劃

思路

這就是最經典的最長公共子序列問題。通常這種求解兩個數組或者字符串求最大或者最小的題目均可以考慮動態規劃,而且一般都定義 dpi 爲 以 A[i], B[j] 結尾的 xxx。這道題就是:以 A[i], B[j] 結尾的兩個數組中公共的、長度最長的子數組的長度框架

關於狀態轉移方程的選擇能夠參考: 穿上衣服我就不認識你了?來聊聊最長上升子序列

算法很簡單:測試

  • 雙層循環找出全部的 i, j 組合,時間複雜度 $O(m * n)$,其中 m 和 n 分別爲 A 和 B 的 長度。spa

    • 若是 A[i] == B[j],dpi = dpi - 1 + 1
    • 不然,dpi = 0
  • 循環過程記錄最大值便可。

記住這個狀態轉移方程,後面咱們還會頻繁用到。code

關鍵點解析

  • dp 建模套路

代碼

代碼支持:Pythonblog

Python Code:

class Solution:
    def findLength(self, A, B):
        m, n = len(A), len(B)
        ans = 0
        dp = [[0 for _ in range(n + 1)] for _ in range(m + 1)]
        for i in range(1, m + 1):
            for j in range(1, n + 1):
                if A[i - 1] == B[j - 1]:
                    dp[i][j] = dp[i - 1][j - 1] + 1
                    ans = max(ans, dp[i][j])
        return ans

複雜度分析

  • 時間複雜度:$O(m * n)$,其中 m 和 n 分別爲 A 和 B 的 長度。
  • 空間複雜度:$O(m * n)$,其中 m 和 n 分別爲 A 和 B 的 長度。
二分查找也是能夠的,不過並不容易想到,你們能夠試試。

1143.最長公共子序列

題目地址

https://leetcode-cn.com/probl...

題目描述

給定兩個字符串  text1 和  text2,返回這兩個字符串的最長公共子序列的長度。

一個字符串的   子序列   是指這樣一個新的字符串:它是由原字符串在不改變字符的相對順序的狀況下刪除某些字符(也能夠不刪除任何字符)後組成的新字符串。
例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。兩個字符串的「公共子序列」是這兩個字符串所共同擁有的子序列。

若這兩個字符串沒有公共子序列,則返回 0。

示例 1:

輸入:text1 = "abcde", text2 = "ace"
輸出:3
解釋:最長公共子序列是 "ace",它的長度爲 3。
示例 2:

輸入:text1 = "abc", text2 = "abc"
輸出:3
解釋:最長公共子序列是 "abc",它的長度爲 3。
示例 3:

輸入:text1 = "abc", text2 = "def"
輸出:0
解釋:兩個字符串沒有公共子序列,返回 0。

提示:

1 <= text1.length <= 1000
1 <= text2.length <= 1000
輸入的字符串只含有小寫英文字符。

前置知識

  • 數組
  • 動態規劃

思路

和上面的題目相似,只不過數組變成了字符串(這個無所謂),子數組(連續)變成了子序列 (非連續)。

算法只須要一點小的微調: 若是 A[i] != B[j],那麼 dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])

關鍵點解析

  • dp 建模套路

代碼

你看代碼多像

代碼支持:Python

Python Code:

class Solution:
    def longestCommonSubsequence(self, A: str, B: str) -> int:
        m, n = len(A), len(B)
        ans = 0
        dp = [[0 for _ in range(n + 1)] for _ in range(m + 1)]
        for i in range(1, m + 1):
            for j in range(1, n + 1):
                if A[i - 1] == B[j - 1]:
                    dp[i][j] = dp[i - 1][j - 1] + 1
                    ans = max(ans, dp[i][j])
                else:
                    dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
        return ans

複雜度分析

  • 時間複雜度:$O(m * n)$,其中 m 和 n 分別爲 A 和 B 的 長度。
  • 空間複雜度:$O(m * n)$,其中 m 和 n 分別爲 A 和 B 的 長度。

1035. 不相交的線

題目地址

https://leetcode-cn.com/probl...

題目描述

咱們在兩條獨立的水平線上按給定的順序寫下  A  和  B  中的整數。

如今,咱們能夠繪製一些鏈接兩個數字  A[i]  和  B[j]  的直線,只要  A[i] == B[j],且咱們繪製的直線不與任何其餘連線(非水平線)相交。

以這種方法繪製線條,並返回咱們能夠繪製的最大連線數。

示例 1:

輸入:A = [1,4,2], B = [1,2,4]
輸出:2
解釋:
咱們能夠畫出兩條不交叉的線,如上圖所示。
咱們沒法畫出第三條不相交的直線,由於從 A[1]=4 到 B[2]=4 的直線將與從 A[2]=2 到 B[1]=2 的直線相交。
示例 2:

輸入:A = [2,5,1,2,5], B = [10,5,2,1,5,2]
輸出:3
示例 3:

輸入:A = [1,3,7,1,7,5], B = [1,9,2,5,1]
輸出:2

提示:

1 <= A.length <= 500
1 <= B.length <= 500
1 <= A[i], B[i] <= 2000

前置知識

  • 數組
  • 動態規劃

思路

從圖中能夠看出,若是想要不相交,則必然相對位置要一致,換句話說就是:公共子序列。所以和上面的 1143.最長公共子序列 同樣,屬於換皮題,代碼也是如出一轍。

關鍵點解析

  • dp 建模套路

代碼

你看代碼多像

代碼支持:Python

Python Code:

class Solution:
    def longestCommonSubsequence(self, A: str, B: str) -> int:
        m, n = len(A), len(B)
        ans = 0
        dp = [[0 for _ in range(n + 1)] for _ in range(m + 1)]
        for i in range(1, m + 1):
            for j in range(1, n + 1):
                if A[i - 1] == B[j - 1]:
                    dp[i][j] = dp[i - 1][j - 1] + 1
                    ans = max(ans, dp[i][j])
                else:
                    dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
        return ans

複雜度分析

  • 時間複雜度:$O(m * n)$,其中 m 和 n 分別爲 A 和 B 的 長度。
  • 空間複雜度:$O(m * n)$,其中 m 和 n 分別爲 A 和 B 的 長度。

總結

第一道是「子串」題型,第二和第三則是「子序列」。不論是「子串」仍是「子序列」,狀態定義都是同樣的,不一樣的只是一點小細節。

只有熟練掌握基礎的數據結構與算法,才能對複雜問題迎刃有餘。 基礎算法,把它完全搞懂,再去面對出題人的各類換皮就不怕了。相反,若是你不去思考題目背後的邏輯,就會刷地很痛苦。題目稍微一變化你就不會了,這也是爲何不少人說刷了不少題,可是碰到新的題目仍是不會作的緣由之一。關注公衆號力扣加加,努力用清晰直白的語言還原解題思路,而且有大量圖解,手把手教你識別套路,高效刷題。

更多題解能夠訪問個人 LeetCode 題解倉庫:https://github.com/azl3979858... 。 目前已經 30K star 啦。

相關文章
相關標籤/搜索