在动态规划中,填表时固定最后一个数还是倒数第二个数,取决于问题的定义和状态转移方程的设计。
目录
[1. 固定最后一个数](#1. 固定最后一个数)
[2. 固定倒数第二个数](#2. 固定倒数第二个数)
[3. 固定最后一个数与倒数第二个数的对比](#3. 固定最后一个数与倒数第二个数的对比)
[4. 总结](#4. 总结)
1. 固定最后一个数
适用场景
-
当问题的解与以某个位置为结尾的子问题相关时,通常固定最后一个数。
-
例如:最大子数组和、最长递增子序列(LIS)等问题。
特点
-
状态定义通常为
dp[i]
,表示以第i
个元素为结尾的最优解。 -
状态转移方程通常依赖于前一个状态(
dp[i-1]
)或前面所有状态。
示例
-
最大子数组和
状态定义:
dp[i]
表示以nums[i]
为结尾的最大子数组和。状态转移:
dp[i] = max(nums[i], dp[i-1] + nums[i])
代码:
cppint maxSubArray(vector<int>& nums) { int dp = nums[0], res = dp; for (int i = 1; i < nums.size(); i++) { dp = max(nums[i], dp + nums[i]); res = max(res, dp); } return res; }
-
最长递增子序列(LIS)
状态定义:
dp[i]
表示以nums[i]
为结尾的最长递增子序列长度。状态转移:
dp[i] = max(dp[i], dp[j] + 1)
,其中j < i
且nums[j] < nums[i]
代码:
cppint lengthOfLIS(vector<int>& nums) { vector<int> dp(nums.size(), 1); int res = 1; for (int i = 1; i < nums.size(); i++) { for (int j = 0; j < i; j++) { if (nums[i] > nums[j]) { dp[i] = max(dp[i], dp[j] + 1); } } res = max(res, dp[i]); } return res; }
2. 固定倒数第二个数
适用场景
-
当问题的解与子问题的中间状态相关时,通常固定倒数第二个数。
-
例如:最长公共子序列(LCS)、编辑距离等问题。
特点
-
状态定义通常为
dp[i][j]
,表示前i
个元素和前j
个元素之间的某种关系。 -
状态转移方程通常依赖于
dp[i-1][j-1]
、dp[i-1][j]
或dp[i][j-1]
。
示例
-
最长公共子序列(LCS)
状态定义:
dp[i][j]
表示字符串A
的前i
个字符和字符串B
的前j
个字符的最长公共子序列长度。状态转移:
cppif (A[i-1] == B[j-1]) { dp[i][j] = dp[i-1][j-1] + 1; } else { dp[i][j] = max(dp[i-1][j], dp[i][j-1]); }
代码:
cppint longestCommonSubsequence(string text1, string text2) { int m = text1.size(), n = text2.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++) { if (text1[i-1] == text2[j-1]) { dp[i][j] = dp[i-1][j-1] + 1; } else { dp[i][j] = max(dp[i-1][j], dp[i][j-1]); } } } return dp[m][n]; }
-
编辑距离
状态定义:
dp[i][j]
表示将字符串A
的前i
个字符转换为字符串B
的前j
个字符所需的最小操作次数。状态转移:
cppif (A[i-1] == B[j-1]) { dp[i][j] = dp[i-1][j-1]; } else { dp[i][j] = min({dp[i-1][j], dp[i][j-1], dp[i-1][j-1]}) + 1; }
代码:
cppint minDistance(string word1, string word2) { int m = word1.size(), n = word2.size(); vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0)); for (int i = 0; i <= m; i++) dp[i][0] = i; for (int j = 0; j <= n; j++) dp[0][j] = j; for (int i = 1; i <= m; i++) { for (int j = 1; j <= n; j++) { if (word1[i-1] == word2[j-1]) { dp[i][j] = dp[i-1][j-1]; } else { dp[i][j] = min({dp[i-1][j], dp[i][j-1], dp[i-1][j-1]}) + 1; } } } return dp[m][n]; }
3. 固定最后一个数与倒数第二个数的对比
特性 | 固定最后一个数 | 固定倒数第二个数 |
---|---|---|
适用问题 | 子数组、子序列问题 | 双序列、矩阵类问题 |
状态定义 | dp[i] 或 dp[i][j] |
dp[i][j] |
状态转移 | 依赖前一个状态或前面所有状态 | 依赖 dp[i-1][j-1] 等中间状态 |
典型问题 | 最大子数组和、LIS | LCS、编辑距离 |
4. 总结
-
固定最后一个数:适用于子数组或子序列问题,状态转移通常依赖于前一个状态或前面所有状态。
-
固定倒数第二个数 :适用于双序列或矩阵类问题,状态转移通常依赖于中间状态(如
dp[i-1][j-1]
)。