基础算法 - 动态规划 - 2 多维动态规划。
相关题目 难度:中等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution { public int uniquePaths (int m, int n) { int [][] dp = new int [m + 1 ][n + 1 ]; dp[0 ][1 ] = 1 ; for (int i = 1 ; i <= m; i++) { for (int j = 1 ; j <= n; j++) { dp[i][j] = dp[i - 1 ][j] + dp[i][j - 1 ]; } } return dp[m][n]; } }
爬楼梯二维版。
状态定义
dp[i][j]
表示从起点 (1, 1)
到位置 (i, j)
的路径数量。
状态转移方程
对于位置 (i, j)
,它的路径数量等于它上方位置 (i-1, j)
和左侧位置 (i, j-1)
的路径数量之和: dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
难度:中等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public int minPathSum (int [][] grid) { int m = grid.length; int n = grid[0 ].length; int [][] dp = new int [m + 1 ][n + 1 ]; for (int i = 1 ; i <= m; i++) { for (int j = 1 ; j <= n; j++) { if (i == 1 ) { dp[i][j] = dp[i][j - 1 ] + grid[i - 1 ][j - 1 ]; } else if (j == 1 ) { dp[i][j] = dp[i - 1 ][j] + grid[i - 1 ][j - 1 ]; } else { dp[i][j] = Math.min(dp[i - 1 ][j], dp[i][j - 1 ]) + grid[i - 1 ][j - 1 ]; } } } return dp[m][n]; } }
状态定义
dp[i][j]
表示从起点 (1, 1)
到位置 (i, j)
的最小路径和。
状态转移
对于位置 (i, j)
,它的最小路径和等于它上方位置 (i-1, j)
和左侧位置 (i, j-1)
的最小路径和加上当前位置的值。
难度:中等
动态规划 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution { public String longestPalindrome (String s) { int len = s.length(); boolean [][] dp = new boolean [len + 2 ][len + 2 ]; for (int i = 0 ; i <= len + 1 ; i++) Arrays.fill(dp[i], true ); int start = -1 ; int end = -1 ; int maxLen = -1 ; for (int i = len; i >= 1 ; i--) { for (int j = i; j <= len; j++) { dp[i][j] = dp[i + 1 ][j - 1 ] && (s.charAt(i - 1 ) == s.charAt(j - 1 )); if (dp[i][j]) { if (j - i + 1 > maxLen) { maxLen = j - i + 1 ; start = i - 1 ; end = j; } } } } return s.substring(start, end); } }
状态定义:
用一个二维布尔数组 dp[i][j]
来表示从第 i
个字符到第 j
个字符是否是回文子串。
dp[i][j]
为 true
表示 s[i-1...j-1]
是回文子串,false
则表示不是。
初始化:
所有 dp[i][i]
都是 true
,因为单个字符是回文。
所有 dp[i][i-n]
也是被设置为true
,这是根据状态转移方程需要而设置的。
状态转移方程:
中心开花法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution { int left; int right; public String longestPalindrome (String s) { left = 0 ; right = 0 ; char [] charArray = s.toCharArray(); for (int i = 0 ; i < charArray.length-1 ; i++) { valid(charArray,i,i); valid(charArray,i,i+1 ); } return new String (charArray,left,right-left+1 ); } public void valid (char [] charArray , int i ,int j) { while (i>= 0 && j < charArray.length && charArray[i] == charArray[j]){ i--; j++; } i++; j--; if (j - i > right -left){ left = i; right = j; } } }
运行速度比动态规划法要快很多。
难度:中等
这个题的难点在于分析出状态转移方程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public int longestCommonSubsequence (String text1, String text2) { int len1 = text1.length(); int len2 = text2.length(); int [][] dp = new int [len1+1 ][len2+1 ]; char [] charArray1 = text1.toCharArray(); char [] charArray2 = text2.toCharArray(); for (int i=1 ; i<=len1; i++) { for (int j=1 ; j<=len2; j++) { if (charArray1[i-1 ] == charArray2[j-1 ]) dp[i][j] = dp[i-1 ][j-1 ] + 1 ; else dp[i][j] = Math.max(dp[i][j-1 ], dp[i-1 ][j]); } } return dp[len1][len2]; } }
定义状态 :
dp[i][j]
表示 text1
的前 i
个字符和 text2
的前 j
个字符的最长公共子序列的长度。
状态转移方程 :
如果 text1[i-1] == text2[j-1]
,那么 dp[i][j] = dp[i-1][j-1] + 1
。
如果 text1[i-1] != text2[j-1]
,那么 dp[i][j] = Math.max(dp[i][j-1], dp[i-1][j])
。
1 2 3 4 text1 = 'abcde' text2 = 'ace' dp[3][2] = dp[2][1]+1; dp[3][3] = max(dp[3][2],dp[2][3]);
难度:中等
本体的难度在于抽象出动态规划的状态和状态转移方程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 class Solution { public int minDistance (String word1, String word2) { int m = word1.length(); int n = word2.length(); int [][] dp = new int [m + 1 ][n + 1 ]; for (int i = 1 ; i <= m; i++) { dp[i][0 ] = i; } for (int j = 1 ; j <= n; j++) { dp[0 ][j] = j; } char [] charArray1 = word1.toCharArray(); char [] charArray2 = word2.toCharArray(); for (int i = 1 ; i <= m; i++) { char char1 = charArray1[i - 1 ]; for (int j = 1 ; j <= n; j++) { char char2 = charArray2[j - 1 ]; int insert_j = dp[i][j - 1 ] + 1 ; int insert_i = dp[i - 1 ][j] + 1 ; int replace = dp[i - 1 ][j - 1 ]; if (char1 != char2) { replace += 1 ; } dp[i][j] = Math.min(Math.min(insert_j, insert_i), replace); } } return dp[m][n]; } }
初始化 :
dp[i][0] = i
:将 word1
的前 i
个字符删除需要 i
步。
dp[0][j] = j
:将 word2
的前 j
个字符插入需要 j
步。
状态转移 :
在word2
中插入 :dp[i][j-1] + 1
对应在 word2
中插入一个字符,使得前 i
个字符能与 word2
的前 j
个字符匹配。
在word1
中插入 : dp[i-1][j] + 1
对应在 word1
中删除一个字符,使得前 i-1
个字符能与 word2
的前 j
个字符匹配。
替换操作 :dp[i-1][j-1] + 1
(如果当前字符不相等)或者 dp[i-1][j-1]
(如果当前字符相等)。
动态规划 :
利用三种操作的最小值更新 dp[i][j]
,从而得到将 word1
的前 i
个字符转换为 word2
的前 j
个字符所需的最小操作数。
结果 :
最后返回 dp[m][n]
,即将整个 word1
转换为 word2
所需的最小操作数。