【動態規劃】 深入動態規劃—兩個數組的dp問題

文章目錄

  • 前言
  • 例題
    • 一、最長公共子序列
    • 二、不相交的線
    • 三、不同的子序列
    • 四、通配符匹配
    • 五、交錯字符串
    • 六、兩個字符串的最小ASCII刪除和
    • 七、最長重復子數組
  • 結語


在這里插入圖片描述

前言

問題本質
它主要圍繞著給定的兩個數組展開,旨在通過對這兩個數組元素間關系的分析,找出滿足特定目標的最優解。比如在字符串處理中,兩個字符串可看作字符數組,求它們的最長公共子序列等問題;或是在數值數組場景下,計算從兩個數組元素組合中得到的最大收益等。
解題關鍵要素
狀態定義:通常構建一個二維數組 dp[i][j] 作為狀態表示。這里的 i 對應第一個數組的下標,j 對應第二個數組的下標 。dp[i][j] 代表在考慮第一個數組前 i 個元素和第二個數組前 j 個元素時,問題的最優解值,像上述最長公共子序列問題中,dp[i][j] 就表示對應前綴子串的最長公共子序列長度。
狀態轉移方程:這是解決問題的核心。需深入剖析問題特性,明確 dp[i][j] 如何由已求解的子問題狀態(如 dp[i - 1][j]、dp[i][j - 1]、dp[i - 1][j - 1] 等)推導得出。例如在求兩個數組元素匹配的最大得分問題中,若當前兩個數組元素匹配,dp[i][j] 可能由 dp[i - 1][j - 1] 加上對應得分轉移而來;若不匹配,則需比較 dp[i - 1][j] 和 dp[i][j - 1] 取較大值。
邊界條件:初始化 dp 數組的第一行和第一列。如在最長公共子序列問題里,dp[0][j] 和 dp[i][0] 都初始化為 0 ,因為一個空串與另一個串不存在公共子序列。
通過合理定義狀態、精準推導狀態轉移方程并正確處理邊界條件,就能借助動態規劃高效解決涉及兩個數組的各類問題。

下面本篇文章,將通過例題為大家詳細動態規劃中的兩個數組的dp問題!

例題

一、最長公共子序列

  1. 題目鏈接:最長公共子序列
  2. 題目描述:

給定兩個字符串 text1 和 text2,返回這兩個字符串的最長公共子序列的長度。如果不存在公共?序 列 ,返回 0 。 ?個字符串的 ?序列 是指這樣?個新的字符串:它是由原字符串在不改變字符的相對順序的情況下 刪除某些字符(也可以不刪除任何字符)后組成的新字符串。 ? 例如,“ace” 是 “abcde” 的?序列,但 “aec” 不是 “abcde” 的?序列。
兩個字符串的 公共?序列 是這兩個字符串所共同擁有的?序列。 ?例 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, text2.length <= 1000
text1 和 text2 僅由?寫英?字符組成。

  1. 解法(動態規劃):
    算法思路:
    狀態表示: 對于兩個數組的動態規劃,我們的定義狀態表?的經驗就是:
    i. 選取第?個數組 [0, i] 區間以及第?個數組 [0, j] 區間作為研究對象;
    ii. 結合題目要求,定義狀態表?。 在這道題中,我們根據定義狀態表示為:
    dp[i][j] 表示: s1 的 [0, i] 區間以及 s2 的 [0, j] 區間內的所有的子序列中,最長公共子序列的長度。
    狀態轉移方程: 分析狀態轉移方程的經驗就是根據「最后?個位置」的狀況,分情況討論。 對于 dp[i][j] ,我們可以根據 s1[i] 與 s2[j] 的字符分情況討論:
    i. 兩個字符相同, s1[i] = s2[j] :那么最長公共子序列就在 s1 的 [0, i - 1] 以 及 s2 的 [0, j - 1] 區間上找到?個最長的,然后再加上 s1[i] 即可。因此dp[i][j] = dp[i - 1][j - 1] + 1 ;
    ii. 兩個字符不相同, s1[i] != s2[j] :那么最長公共子序列?定不會同時以 s1[i]
    和 s2[j] 結尾。那么我們找最?公共?序列時,有下面三種策略:
    ? 去 s1 的 [0, i - 1] 以及 s2 的 [0, j] 區間內找:此時最??度為 dp[i - 1][j] ; ? 去 s1 的 [0, i] 以及 s2 的 [0, j - 1] 區間內找:此時最大長度為 dp[i][j - 1] ;
    ? 去 s1 的 [0, i - 1] 以及 s2 的 [0, j - 1] 區間內找:此時最大長度為dp[i - 1][j - 1] 。 我們要三者的最大值即可。但是我們細細觀察會發現,第三種包含在第?種和第二種情況里面,但是我們求的是最大值,并不影響最終結果。因此只需求前兩種情況下的最大值即可。 綜上,狀態轉移方程為:
    if(s1[i] == s2[j]) dp[i][j] = dp[i - 1][j - 1] + 1 ;
    if(s1[i] != s2[j]) dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) 。
    初始化:
    a. 「空串」是有研究意義的,因此我們將原始 dp 表的規模多加上??和?列,表?空串。
    b. 引入空串后,大大的方便我們的初始化。
    c. 但也要注意「下標的映射關系」,以及??的值要「保證后續填表是正確的」。
    當 s1 為空時,沒有長度,同理 s2 也是。因此第??和第?列里面的值初始化為 0 即可保證后續填表是正確的。
    填表順序: 根據「狀態轉移方程」得:從上往下填寫每?行,每?行從左往右。
    返回值: 根據「狀態表示」得:返回 dp[m][n]

  2. 代碼示例:

 public int longestCommonSubsequence(String s1, String s2) {// 1. 創建 dp 表// 2. 初始化// 3. 填表// 4. 返回值int m = s1.length(), n = s2.length();s1 = " " + s1;s2 = " " + s2;int[][] dp = new int[m + 1][n + 1];for (int i = 1; i <= m; i++)for (int j = 1; j <= n; j++)if (s1.charAt(i) == s2.charAt(j))dp[i][j] = dp[i - 1][j - 1] + 1;elsedp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);return dp[m][n];}

二、不相交的線

  1. 題目鏈接:不相交的線
  2. 題目描述:

在兩條獨立的水平線上按給定的順序寫下 nums1 和 nums2 中的整數。 現在,可以繪制?些連接兩個數字 nums1[i] 和 nums2[j] 的直線,這些直線需要同時滿足:
? nums1[i] == nums2[j]
? 且繪制的直線不與任何其他連線(非水平線)相交。
請注意,連線即使在端點也不能相交:每個數字只能屬于?條連線。 以這種方法繪制線條,并返回可以繪制的最大連線數。
示例 1:
在這里插入圖片描述
輸?:nums1 = [1,4,2], nums2 = [1,2,4]
輸出:2 解釋:可以畫出兩條不交叉的線,如上圖所示。 但無法畫出第三條不相交的直線,因為從 nums1[1]=4 到 nums2[2]=4 的直線將與從nums1[2]=2 到 nums2[1]=2 的直線相交。
示例 2: 輸入:nums1 = [2,5,1,2,5], nums2 = [10,5,2,1,5,2]
輸出:3
示例 3: 輸入:nums1 = [1,3,7,1,7,5], nums2 = [1,9,2,5,1]
輸出:2

  1. 解法(動態規劃):
    算法思路: 如果要保證兩條直線不相交,那么我們「下?個連線」必須在「上?個連線」對應的兩個元素的 「后面」尋找相同的元素。這不就轉化成「最長公共子序列」的模型了嘛。那就是在這兩個數組中 尋找「最長的公共子序列」。
    只不過是在整數數組中做?次「最長的公共子序列」,代碼幾乎?模?樣,這里就不再贅述算法原理啦~

  2. 代碼示例:

 public int maxUncrossedLines(int[] nums1, int[] nums2) {// 1. 創建 dp 表// 2. 初始化// 3. 填表// 4. 返回值int m = nums1.length, n = nums2.length;int[][] dp = new int[m + 1][n + 1];for (int i = 1; i <= m; i++)for (int j = 1; j <= n; j++)if (nums1[i - 1] == nums2[j - 1]) dp[i][j] = dp[i - 1][j - 1] +1;else dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);return dp[m][n];}

三、不同的子序列

  1. 題目鏈接:不同的子序列
  2. 題目描述:

給定一個字符串 s 和一個字符串 t ,計算在 s 的子序列中 t 出現的個數。 字符串的?個子序列是指,通過刪除?些(也可以不刪除)字符且不干擾剩余字符相對位置所組成的新字符串。(例如,“ACE” 是 “ABCDE” 的?個子序列,而 “AEC” 不是) 題目數據保證答案符合 32 位帶符號整數范圍。
示例 1:輸入:s = “rabbbit”, t = “rabbit” 輸出:3 解釋:如下圖所示, 有 3 種可以從 s 中得到 “rabbit” 的?案。
rabbbit
rabbbit
rabbbit
示例 2:輸?:s = “babgbag”, t = “bag” 輸出:5
解釋:如下圖所示, 有 5 種可以從 s 中得到 “bag” 的?案。
babgbag
babgbag
babgbag
babgbag
babgbag

  1. 解法(動態規劃): 算法思路:
    狀態表示: 對于兩個字符串之間的 dp 問題,我們?般的思考方式如下:
    i. 選取第?個字符串的 [0, i] 區間以及第?個字符串的 [0, j] 區間當成研究對象,結 合題目的要求來定義「狀態表示」;
    ii. 然后根據兩個區間上「最后?個位置的字符」,來進行「分類討論」,從而確定「狀態轉移方程」。
    我們可以根據上面的策略,解決大部分關于兩個字符串之間的 dp 問題。
    dp[i][j] 表示:在字符串 s 的 [0, j] 區間內的所有子序列中,有多少個 t 字符串 [0,
    i] 區間內的子串。
    狀態轉移方程: 老規矩,根據「最后?個位置」的元素,結合題目要求,分情況討論:
    i. 當 t[i] == s[j] 的時候,此時的?序列有兩種選擇: ? ?種選擇是:子序列選擇 s[j] 作為結尾,此時相當于在狀態 dp[i - 1][j - 1]
    中的所有符合要求的?序列的后?,再加上?個字符 s[j] (請大家結合狀態表示,好好理解這句話),此時 dp[i][j] = dp[i - 1][j - 1] ; ? 另?種選擇是:我就是任性,我就不選擇 s[j] 作為結尾。此時相當于選擇了狀態dp[i][j - 1] 中所有符合要求的?序列。我們也可以理解為繼承了上個狀態??的 求得的?序列。此時 dp[i][j] = dp[i][j - 1] ; 兩種情況加起來,就是 t[i] == s[j] 時的結果。
    ii. 當 t[i] != s[j] 的時候,此時的?序列只能從 dp[i][j - 1] 中選擇所有符合要求的子序列。只能繼承上個狀態里面求得的子序列, dp[i][j] = dp[i][j - 1] ; 綜上所述,狀態轉移方程為:
    ? 所有情況下都可以繼承上?次的結果: dp[i][j] = dp[i][j - 1] ; ? 當 t[i] == s[j] 時,可以多選擇?種情況: dp[i][j] += dp[i - 1][j - 1]
    初始化:
    a. 「空串」是有研究意義的,因此我們將原始 dp 表的規模多加上??和?列,表?空串。
    b. 引?空串后,??的?便我們的初始化。
    c. 但也要注意「下標的映射關系」,以及??的值要「保證后續填表是正確的」。
    當 s 為空時, t 的子串中有?個空串和它?樣,因此初始化第??全部為 1 。
    填表順序: 「從上往下」填每?行,每?行「從左往右」。
    返回值:根據「狀態表?」,返回 dp[m][n] 的值。

  2. 代碼示例:

 public int numDistinct(String s, String t) {// 1. 創建 dp 表// 2. 初始化// 3. 填表// 4. 返回值int m = t.length(), n = s.length();int[][] dp = new int[m + 1][n + 1];for (int j = 0; j <= n; j++) dp[0][j] = 1;for (int i = 1; i <= m; i++)for (int j = 1; j <= n; j++) {dp[i][j] = dp[i][j - 1];if (t.charAt(i - 1) == s.charAt(j - 1))dp[i][j] += dp[i - 1][j - 1];}return dp[m][n];}

四、通配符匹配

  1. 題目鏈接:通配符匹配
  2. 題目描述:

給定?個字符串 (s) 和?個字符模式 § ,實現?個?持 ‘?’ 和 ’ * ’ 的通配符匹配。
‘?’ 可以匹配任何單個字符。
’ * ’ 可以匹配任意字符串(包括空字符串)。
兩個字符串完全匹配才算匹配成功。 說明: s 可能為空,且只包含從 a-z 的小寫字母。 p 可能為空,且只包含從 a-z 的小寫字母,以及字符 ? 和
示例 1:
輸?: s = “aa” p = “a” 輸出: false
解釋: “a” 無法匹配 “aa” 整個字符串。
示例 2:
輸入: s = “aa” p = "
"
輸出: true
解釋: ‘’ 可以匹配任意字符串。
示例 3:
輸入: s = “cb” p = “?a” 輸出: false
解釋: ‘?’ 可以匹配 ‘c’, 但第?個 ‘a’ ?法匹配 ‘b’。 ?例 4:
輸入: s = “adceb” p = “ab” 輸出: true
解釋: 第?個 '
’ 可以匹配空字符串, 第?個 '’ 可以匹配字符串 “dce”. ?例 5:
輸?: s = “acdcb” p = "a
c?b" 輸出: false

  1. 解法(動態規劃):
    算法思路:
    狀態表示: 對于兩個字符串之間的 dp 問題,我們?般的思考?式如下:
    i. 選取第?個字符串的 [0, i] 區間以及第二個字符串的 [0, j] 區間當成研究對象,結合題目的要求來定義「狀態表示」;
    ii. 然后根據兩個區間上「最后?個位置的字符」,來進行「分類討論」,從而確定「狀態轉移方程」。
    我們可以根據上?的策略,解決大部分關于兩個字符串之間的 dp 問題。 因此,我們定義狀態表示為:
    dp[i][j] 表示: p 字符串 [0, j] 區間內的子串能否匹配字符串 s 的 [0, i] 區間內的 ?串。
    狀態轉移方程: 老規矩,根據最后?個位置的元素,結合題目要求,分情況討論:
    i. 當 s[i] == p[j] 或 p[j] == ‘?’ 的時候,此時兩個字符串匹配上了當前的?個字 符,只能從 dp[i - 1][j - 1] 中看當前字符前?的兩個子串是否匹配。只能繼承上個狀態中的匹配結果, dp[i][j] = dp[i][j - 1] ;
    ii. 當 p[j] == ‘’ 的時候,此時匹配策略有兩種選擇:
    ? ?種選擇是: * 匹配空字符串,此時相當于它匹配了?個寂寞,直接繼承狀態 dp[i][j - 1] ,此時 dp[i][j] = dp[i][j - 1] ;
    ? 另?種選擇是: * 向前匹配 1 ~ n 個字符,直?匹配上整個 s1 串。此時相當于 從 dp[k][j - 1] (0 <= k <= i) 中所有匹配情況中,選擇性繼承可以成功的 情況。此時 dp[i][j] = dp[k][j - 1] (0 <= k <= i) ;
    iii. 當 p[j] 不是特殊字符,且不與 s[i] 相等時,無法匹配。 三種情況加起來,就是所有可能的匹配結果。 綜上所述,狀態轉移?程為: ? 當 s[i] == p[j] 或 p[j] == ‘?’ 時: dp[i][j] = dp[i][j - 1] ; ? 當 p[j] == '
    ’ 時,有多種情況需要討論: dp[i][j] = dp[k][j - 1] (0 <= k <= i) ; 優化:當我們發現,計算?個狀態的時候,需要?個循環才能搞定的時候,我們要想到去優化。優 化的?向就是用?個或者兩個狀態來表示這?堆的狀態。通常就是把它寫下來,然后?數學的方式 做?下等價替換: 當 p[j] == '’ 時,狀態轉移方程為:dp[i][j] = dp[i][j - 1] || dp[i - 1][j - 1] || dp[i - 2][j - 1] …
    我們發現 i 是有規律的減小的,因此我們去看看 dp[i - 1][j] :dp[i - 1][j] = dp[i - 1][j - 1] || dp[i - 2][j - 1] || dp[i - 3][j - 1] …
    我們驚奇的發現, dp[i][j] 的狀態轉移?程??除了第?項以外,其余的都可以用 dp[i - 1][j] 替代。因此,我們優化我們的狀態轉移?程為: dp[i][j] = dp[i - 1][j] || dp[i][j - 1] 。
    初始化: 由于 dp 數組的值設置為是否匹配,為了不與答案值混淆,我們需要將整個數組初始化為
    false 。 由于需要用到前一行和前一列的狀態,我們初始化第一行、第?列即可。
    ? dp[0][0] 表?兩個空串能否匹配,答案是顯然的, 初始化為 true 。
    ? 第一行表示 s 是?個空串, p 串和空串只有?種匹配可能,即 p 串表示為 "
    *" ,此時 也相當于空串匹配上空串。所以,我們可以遍歷 p 串,把所有前導為 "" 的 p ?串和空串 的 dp 值設為 true 。 ? 第?列表示p 是?個空串,不可能匹配上 s 串,跟隨數組初始化即可。
    填表順序:
    從上往下填每一行,每?行從左往右。
    返回值: 根據狀態表示,返回 dp[m][n] 的值。

  2. 代碼示例

 public boolean isMatch(String ss, String pp) {// 1. 創建 dp 表// 2. 初始化// 3. 填表// 4. 返回結果int m = ss.length(), n = pp.length();ss = " " + ss;pp = " " + pp;char[] s = ss.toCharArray();char[] p = pp.toCharArray();boolean[][] dp = new boolean[m + 1][n + 1];dp[0][0] = true;for (int j = 1; j <= n; j++)if (p[j] == '*') dp[0][j] = true;else break;for (int i = 1; i <= m; i++)for (int j = 1; j <= n; j++)if (p[j] == '*')dp[i][j] = dp[i - 1][j] || dp[i][j - 1];elsedp[i][j] = (p[j] == '?' || p[j] == s[i]) && dp[i - 1][j -1];return dp[m][n];}

五、交錯字符串

  1. 題目鏈接:
  2. 題目描述:

給定三個字符串 s1、s2、s3,請你幫忙驗證 s3 是否是由 s1 和 s2 交錯組成的。
兩個字符串 s 和 t 交錯 的定義與過程如下,其中每個字符串都會被分割成若干非空子字符串: s = s1 + s2 + … + sn
t = t1 + t2 + … + tm |n - m| <= 1 交錯 是 s1 + t1 + s2 + t2 + s3 + t3 + … 或者 t1 + s1 + t2 + s2 + t3 + s3 + …
注意:a + b 意味著字符串 a 和 b 連接。
示例 1:
在這里插入圖片描述
輸?:s1 = “aabcc”, s2 = “dbbca”, s3 = “aadbbcbcac” 輸出:true
示例 2:輸入:s1 = “aabcc”, s2 = “dbbca”, s3 = “aadbbbaccc”
輸出:false
示例 3:輸入:s1 = “”, s2 = “”, s3 = “”
輸出:true

  1. 解法(動態規劃): 算法思路: 對于兩個字符串之間的 dp 問題,我們?般的思考?式如下:
    i. 選取第?個字符串的 [0, i] 區間以及第?個字符串的 [0, j] 區間當成研究對象,結 合題?的要求來定義「狀態表?」;
    ii. 然后根據兩個區間上「最后?個位置的字符」,來進?「分類討論」,從?確定「狀態轉移 ?程」。
    我們可以根據上?的策略,解決?部分關于兩個字符串之間的 dp 問題。
    這道題??空串是有研究意義的,因此我們先預處理?下原始字符串,前?統?加上?個占位符:
    s1 = " " + s1, s2 = " " + s2, s3 = " " + s3 。
    狀態表?:
    dp[i][j] 表?字符串 s1 中 [1, i] 區間內的字符串以及 s2 中 [1, j] 區間內的字符 串,能否拼接成 s3 中 [1, i + j] 區間內的字符串。
    狀態轉移?程: 先分析?下題目,題目中交錯后的字符串為 s1 + t1 + s2 + t2 + s3 + t3… ,看 似?個 s ?個 t 。實際上 s1 能夠拆分成更小的?個字符,進而可以細化成 s1 + s2 + s3 + t1 + t2 + s4… 。 也就是說,并不是前?個用了 s 的子串,后?個必須要用 t 的子串。這?點理解,對我們的狀態轉移很重要。
    繼續根據兩個區間上「最后?個位置的字符」,結合題目的要求,來進行「分類討論」:
    i. 當 s3[i + j] = s1[i] 的時候,說明交錯后的字符串的最后?個字符和 s1 的最后? 個字符匹配了。那么整個字符串能否交錯組成,變成:s1 中 [1, i - 1] 區間上的字符串以及 s2 中 [1, j] 區間上的字符串,能夠交 錯形成 s3 中 [1, i + j - 1] 區間上的字符串,也就是 dp[i - 1][j];此時 dp[i][j] = dp[i - 1][j]
    ii. 當 s3[i + j] = s2[j] 的時候,說明交錯后的字符串的最后?個字符和 s2 的最后 ?個字符匹配了。那么整個字符串能否交錯組成,變成:s1 中 [1, i] 區間上的字符串以及 s2 中 [1, j - 1] 區間上的字符串,能夠交 錯形成 s3 中 [1, i + j - 1] 區間上的字符串,也就是 dp[i][j - 1] ;
    iii. 當兩者的末尾都不等于 s3 最后?個位置的字符時,說明不可能是兩者的交錯字符串。 上述三種情況下,只要有?個情況下能夠交錯組成目標串,就可以返回 true 。因此,我們可以定義狀態轉移為:
    dp[i][j] = (s1[i - 1] == s3[i + j - 1] && dp[i - 1][j]) || (s2[j - 1] == s3[i + j - 1] && dp[i][j - 1])
    只要有?個成立,結果就是 true 。
    初始化: 由于用到 i - 1 , j - 1 位置的值,因此需要初始化「第?個位置」以及「第?行」和「第? 列」。
    ? 第?個位置:
    dp[0][0] = true ,因為空串 + 空串能夠構成?個空串。
    ? 第?行: 第?行表示 s1 是?個空串,我們只用考慮 s2 即可。因此狀態轉移之和 s2 有關:
    dp[0][j] = s2[j - 1] == s3[j - 1] && dp[0][j - 1] , j 從 1 到 n ( n 為 s2 的?度) ? 第?列: 第?列表? s2 是?個空串,我們只?考慮 s1 即可。因此狀態轉移之和 s1 有關:
    dp[i][0] = s1[i - 1] == s3[i - 1] && dp[i - 1][0] , i 從 1 到 m ( m 為 s1 的長度)
    填表順序: 根據「狀態轉移」,我們需要「從上往下」填每?行,每一行「從左往右」。
    返回值: 根據「狀態表示」,我們需要返回 dp[m][n] 的值。

  2. 代碼示例:

public boolean isInterleave(String s1, String s2, String s3) {// 1. 創建 dp 表// 2. 初始化// 3. 填表// 4. 返回值int m = s1.length(), n = s2.length();if (m + n != s3.length()) return false; // 處理下邊界條件s1 = " " + s1;s2 = " " + s2;s3 = " " + s3;boolean[][] dp = new boolean[m + 1][n + 1];dp[0][0] = true; // 初始化for (int j = 1; j <= n; j++) // 初始化第??if (s2.charAt(j) == s3.charAt(j)) dp[0][j] = true;else break;for (int i = 1; i <= m; i++) // 初始化第?列if (s1.charAt(i) == s3.charAt(i)) dp[i][0] = true;else break;for (int i = 1; i <= m; i++)for (int j = 1; j <= n; j++)dp[i][j] = (s1.charAt(i) == s3.charAt(i + j) && dp[i - 1][j])|| (s2.charAt(j) == s3.charAt(i + j) && dp[i][j - 1]);return dp[m][n];}

六、兩個字符串的最小ASCII刪除和

  1. 題目鏈接:兩個字符串的最小ASCII刪除和
  2. 題目描述:

給定兩個字符串s1 和 s2,返回 使兩個字符串相等所需刪除字符的 ASCII 值的最小和 。
示例 1:輸?: s1 = “sea”, s2 = “eat” 輸出: 231
解釋:在 “sea” 中刪除 “s” 并將 “s” 的值(115)加?總和。 在 “eat” 中刪除 “t” 并將 116 加?總和。 結束時,兩個字符串相等,115 + 116 = 231 就是符合條件的最?和。 ?例 2:
輸?: s1 = “delete”, s2 = “leet” 輸出: 403
解釋:
在 “delete” 中刪除 “dee” 字符串變成 “let”, 將 100[d]+101[e]+101[e] 加?總和。在 “leet” 中刪除 “e” 將 101[e] 加?總和。 結束時,兩個字符串都等于 “let”,結果即為 100+101+101+101 = 403 。 如果改為將兩個字符串轉換為 “lee” 或 “eet”,我們會得到 433 或 417 的結果,比答案更大。

  1. 解法(動態規劃):
    算法思路: 正難則反:求兩個字符串的最小ASCII 刪除和,其實就是找到兩個字符串中所有的公共子序列里面, ASCII 最大和。 因此,我們的思路就是按照「最長公共子序列」的分析方式來分析。
    狀態表示:
    dp[i][j] 表示: s1 的 [0, i] 區間以及 s2 的 [0, j] 區間內的所有的子序列中,公 共?序列的 ASCII 最?和。
    狀態轉移?程: 對于 dp[i][j] 根據「最后?個位置」的元素,結合題目要求,分情況討論:
    i. 當 s1[i] == s2[j] 時:應該先在 s1 的 [0, i - 1] 區間以及 s2 的 [0, j - 1] 區間內找?個公共?序列的最?和,然后在它們后面加上?個 s1[i] 字符即可。 此時 dp[i][j] = dp[i - 1][j - 1] + s1[i] ;
    ii. 當 s1[i] != s2[j] 時:公共?序列的最大和會有三種可能:
    ? s1 的 [0, i - 1] 區間以及 s2 的 [0, j] 區間內:此時 dp[i][j] = dp[i - 1][j] ; ? s1 的 [0, i] 區間以及 s2 的 [0, j - 1] 區間內:此時 dp[i][j] = dp[i][j - 1] ; ? s1 的 [0, i - 1] 區間以及 s2 的 [0, j - 1] 區間內:此時 dp[i][j] = dp[i - 1][j - 1] 。 但是前兩種情況??包含了第三種情況,因此僅需考慮前兩種情況下的最?值即可。 綜上所述,狀態轉移方程為:
    ? 當 s1[i - 1] == s2[j - 1] 時, dp[i][j] = dp[i - 1][j - 1] + s1[i] ;
    ? 當 s1[i - 1] != s2[j - 1] 時, dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
    初始化:
    a. 「空串」是有研究意義的,因此我們將原始 dp 表的規模多加上??和?列,表?空串。
    b. 引?空串后,大大的「方便我們的初始化」。
    c. 但也要注意「下標的映射」關系,以及里面的值要保證「后續填表是正確的」。
    當 s1 為空時,沒有長度,同理 s2 也是。因此第一行和第?列里面的值初始化為 0 即可保證后續填表是正確的。
    填表順序: 「從上往下」填每一行,每?行「從左往右」。
    返回值: 根據「狀態表示」,我們不能直接返回 dp 表里面的某個值:
    i. 先找到 dp[m][n] ,也是最大公共 ASCII 和;
    ii. 統計兩個字符串的 ASCII 碼和 s u m;
    iii. 返回 sum - 2 * dp[m][n]

  2. 代碼示例:

 public int minimumDeleteSum(String s1, String s2) {// 1. 創建 dp 表// 2. 初始化// 3. 填表// 4. 返回值int m = s1.length(), n = s2.length();int[][] dp = new int[m + 1][n + 1];for (int i = 1; i <= m; i++)for (int j = 1; j <= n; j++) {dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);if (s1.charAt(i - 1) == s2.charAt(j - 1))dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - 1] + s1.charAt(i - 1));}int sum = 0; // 統計元素和for (char ch : s1.toCharArray()) sum += ch;for (char ch : s2.toCharArray()) sum += ch;return sum - dp[m][n] - dp[m][n];}

七、最長重復子數組

  1. 題目鏈接:最長重復子數組
  2. 題目描述:

給兩個整數數組 nums1 和 nums2 ,返回兩個數組中公共的 、長度最長的子數組的長度 。
示例 1: 輸入:nums1 = [1,2,3,2,1], nums2 = [3,2,1,4,7]
輸出:3 解釋:長度最長的公共子數組是 [3,2,1] 。
示例 2: 輸入:nums1 = [0,0,0,0,0], nums2 = [0,0,0,0,0]
輸出:5

  1. 解法(動態規劃): 算法思路: ?數組是數組中「連續」的?段,我們習慣上「以某?個位置為結尾」來研究。由于是兩個數組, 因此我們可以嘗試:以第?個數組的 i 位置為結尾以及第?個數組的 j 位置為結尾來解決問題。
    狀態表示:
    dp[i][j] 表示「以第?個數組的 i 位置為結尾」,以及「第?個數組的 j 位置為結尾公共的、長度最長的「子數組」的長度。
    狀態轉移?程: 對于 dp[i][j] ,當 nums1[i] == nums2[j] 的時候,才有意義,此時最長重復子數組的長度應該等于 1 加上除去最后?個位置時,以 i - 1, j - 1 為結尾的最長重復子數組的長度。因此,狀態轉移方程為: dp[i][j] = 1 + dp[i - 1][j - 1]
    初始化:為了處理「越界」的情況,我們可以添加?行和一列, dp 數組的下標從 1 開始,這樣就無需初始化。 第?行表示第?個數組為空,此時沒有重復子數組,因此里面的值設置成 0 即可; 第一列也是同理。
    填表順序: 根據「狀態轉移」,我們需要「從上往下」填每?行,每?行「從左往右」。
    返回值: 根據「狀態表示」,我們需要返回 dp 表里面的「最大值」。

  2. 代碼示例:

 public int findLength(int[] nums1, int[] nums2) {// 1. 創建 dp 表// 2. 初始化// 3. 填表// 4. 返回值int m = nums1.length, n = nums2.length;int[][] dp = new int[m + 1][n + 1];int ret = 0;for (int i = 1; i <= m; i++)for (int j = 1; j <= n; j++)if (nums1[i - 1] == nums2[j - 1]) {dp[i][j] = dp[i - 1][j - 1] + 1;ret = Math.max(ret, dp[i][j]);}return ret;}

結語

本文到這里就結束了,主要通過幾道兩個數組dp問題算法題,介紹了這種類型動態規劃的做題思路,帶大家深入了解了動態規劃中兩個數組dp問題 算法這一類型。

以上就是本文全部內容,感謝各位能夠看到最后,如有問題,歡迎各位大佬在評論區指正,希望大家可以有所收獲!創作不易,希望大家多多支持!

最后,大家再見!祝好!我們下期見!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/76260.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/76260.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/76260.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【C++面向對象】封裝(上):探尋構造函數的幽微之境

每文一詩 &#x1f4aa;&#x1f3fc; 我本將心向明月&#xff0c;奈何明月照溝渠 —— 元/高明《琵琶記》 譯文&#xff1a;我本是以真誠的心來對待你&#xff0c;就像明月一樣純潔無瑕&#xff1b;然而&#xff0c;你卻像溝渠里的污水一樣&#xff0c;對這份心意無動于衷&a…

JavaScript性能優化(下)

1. 使用適當的算法和邏輯 JavaScript性能優化是一個復雜而重要的話題&#xff0c;尤其是在構建大型應用時。通過使用適當的算法和邏輯&#xff0c;可以顯著提高代碼的效率和響應速度。以下是一些關鍵策略和實踐&#xff0c;用于優化JavaScript性能&#xff1a; 1.1. 采用適當…

螞蟻 Flink 實時計算編譯任務 Koupleless 架構改造

張馮君&#xff08;遠遠&#xff09; Koupleless PMC 螞蟻集團技術工程師 就職于螞蟻集團中間件團隊&#xff0c;參與維護與建設螞蟻 SOFAArk 和 Koupleless 開源項目、內部 SOFAServerless 產品的研發和實踐。 本文 3488 字&#xff0c;預計閱讀 11 分鐘 業務背景 基于開源 A…

使用pycharm社區版調試DIFY后端python代碼

目錄 背景 前置條件 DIFY使用的框架 API服務調試配置步驟&#xff08;基于tag為0.15.3的版本&#xff09; 1.配置.env文件 2.關閉docker里面的docker-api-1服務 3.使用DOCKER啟動本地環境需要用到的中間件&#xff0c;并暴露端口 注意事項一&#xff1a; 注意事項二&#xff1a…

從 macos 切換到 windows 上安裝的工具類軟件

起因 用了很多年的macos, 已經習慣了macos上的操作, 期望能在windows上獲得類似的體驗, 于是花了一些時間來找windows上相對應的軟件. 截圖軟件 snipaste?????? windows和macos都有的軟件, 截圖非常好用 文件同步軟件 oneDrive: 嘗試了不同的同步軟件, 還是微軟在各…

MySQL體系架構(一)

1.1.MySQL的分支與變種 MySQL變種有好幾個,主要有三個久經考驗的主流變種:Percona Server,MariaDB和 Drizzle。它們都有活躍的用戶社區和一些商業支持,均由獨立的服務供應商支持。同時還有幾個優秀的開源關系數據庫,值得我們了解一下。 1.1.1.Drizzle Drizzle是真正的M…

【項目實訓項目博客】prompt初版實踐

通過對camel技術的理解&#xff0c;我們向其中添加了市場營銷角色的prompt 初版設計如下&#xff1a; chatchainconfig.json { "chain": [ { "phase": "DemandAnalysis", "phaseType": "SimplePhase", "max_turn_step…

[Bond的雜貨鋪] CKS 證書也到貨咯

最近比較忙&#xff0c;忘記寫Blog了&#xff1a;&#xff09; 一年前黑五去官網蹲了一手Cyber Monday&#xff0c;買了英文考試券bundle&#xff0c;當時只考了cka,后來cks差點都忘記了。將近一年后&#xff0c;無意中收到官方的提醒郵件&#xff0c;說考試券本已過期&#x…

【回眸】Linux 內核 (十五) 之 多線程編程 上

前言 進程和線程 區別 線程API 1.創建線程 2.線程退出 3.線程等待 4.線程脫離 5. 線程ID獲取及比較 6.創建及銷毀互斥鎖 7.創建及銷毀條件變量 8. 等待 9.觸發 多線程編程 后記 前言 高產的幾天。 進程和線程 區別 進程——資源分配的最小單位&#xff0c;線…

127.0.0.1本地環回地址(Loopback Address)

127.0.0.1 是計算機網絡中的一個特殊IPv4地址&#xff0c;稱為本地環回地址&#xff08;Loopback Address&#xff09;&#xff0c;主要用于以下用途&#xff1a; 1. 基本定義 本地主機&#xff08;Localhost&#xff09;&#xff1a;該地址始終指向當前正在使用的計算機本身&a…

S7-1200 PLC熱電偶和熱電阻模擬量模塊

熱電偶和熱電阻模擬量模塊 S7-1200 PLC有專用用于對溫度進行采集的熱電偶模塊SM1231 TC和SM 1231RTD。熱電偶模塊有4AI和8AI兩種&#xff0c;下面以SM1231 TC 4AI為例看一下接線圖。 該模塊一共有4個通道&#xff0c;每個通道有兩個接線端子&#xff0c;比如0&#xff0c;0-。…

深度了解向量引論

今天去研究了一個基本數學原理 這個其實需要證明 今天推導了一下這個公式&#xff0c;感覺收獲挺大 下面是手工推導過程

Feign修仙指南:聲明式HTTP請求的優雅之道

各位在微服務世界摸爬滾打的道友們&#xff01;今天要解鎖的是Spring Cloud的絕世神通——Feign&#xff01;這貨堪稱HTTP界的"言出法隨"&#xff0c;只需定義接口&#xff0c;就能自動生成HTTP請求代碼&#xff01;從此告別手動拼裝URL的苦日子&#xff0c;讓你的代…

UDP學習筆記(四)UDP 為什么大小不能超過 64KB?

&#x1f310; UDP 為什么大小不能超過 64KB&#xff1f;TCP 有這個限制嗎&#xff1f; 在進行網絡編程或者調試網絡協議時&#xff0c;我們常常會看到一個說法&#xff1a; “UDP 最大只能發送 64KB 數據。” 這到底是怎么回事&#xff1f;這 64KB 是怎么來的&#xff1f;TCP…

LabVIEW 中串口設備與采集卡的同步精度

在 LabVIEW 項目開發中&#xff0c;常涉及多種設備協同工作&#xff0c;如通過串口設備采集溫度&#xff0c;利用采集卡&#xff08;如 NI 6251&#xff09;采集壓力。此時&#xff0c;設備間的同步精度至關重要&#xff0c;它直接影響系統數據的準確性與可靠性。下面&#xff…

DP_AUX輔助通道介紹

DisplayPort&#xff08;簡稱DP&#xff09;是一個由PC及芯片制造商聯盟開發&#xff0c;視頻電子標準協會&#xff08;VESA&#xff09;標準化的數字式視頻接口標準。該接口免認證、免授權金&#xff0c;主要用于視頻源與顯示器等設備的連接&#xff0c;并也支持攜帶音頻、USB…

[GESP202312 五級] 平均分配

文章目錄 題目描述輸入格式輸出格式輸入輸出樣例 #1輸入 #1輸出 #1 輸入輸出樣例 #2輸入 #2輸出 #2 提交鏈接提示解析參考代碼 題目描述 小楊認為&#xff0c;所有大于等于 a a a 的完全平方數都是他的超級幸運數。 小楊還認為&#xff0c;所有超級幸運數的倍數都是他的幸運…

[Mysql]buffersize修改

1、找到my.cnf文件位置 ps -ef|grep mysqld 2、編輯my.cnf cd /etc/my.cnf.d vim my.cnf 一般修改為內存的50%~70% 3、重啟服務 systemctl restart mysqld

清晰易懂的 Apollo 配置中心安裝與使用教程

Apollo 是攜程開源的分布式配置管理平臺&#xff0c;支持配置實時推送、版本管理、權限控制等功能。本教程將手把手教你完成 Apollo 核心組件安裝、基礎配置管理及避坑指南&#xff0c;助你快速掌握企業級配置管理能力。 一、環境準備&#xff08;關鍵依賴&#xff09; 1. 基礎…

PyTorch池化層詳解:原理、實現與示例

池化層&#xff08;Pooling Layer&#xff09;是卷積神經網絡中的重要組成部分&#xff0c;主要用于降低特征圖的空間維度、減少計算量并增強模型的平移不變性。本文將通過PyTorch代碼演示池化層的實現原理&#xff0c;并詳細講解最大池化、平均池化、填充&#xff08;Padding&…