写在前面:动态规划的题目和知识点简直浩如烟海,特别基础的有,特别难的也有,我这里只面向面试,整理一些经常出现的。
【一张思维导图】
1. 线性dp
1.1 最长公共子序列(LCS)
// dp[i][j]表示text1的[0,i],和text2的[0,j]的最长公共子序列的长度
// 相等就+1,不等就找大的
func longestCommonSubsequence(text1 string, text2 string) int {
m, n := len(text1), len(text2)
dp := make([][]int, m + 1)
for i := range dp {
dp[i] = make([]int, n + 1)
}
for i := 1; i <= m; i++ {
for j := 1; j <= n; j++ {
a, b := text1[i-1], text2[j-1]
if a == b {
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]
}
1.2 最长递增子序列(LIS)
// dp[i] 表示:以nums[i]结尾的最长递增子序列长度
// 递推公式:[0,i-1] 中,找结尾小于nums[i]的最长的子序列,dp[i]在此基础上+1
func lengthOfLIS(nums []int) int {
n := len(nums)
dp := make([]int, n)
ans := 0
for i := 0; i < n; i++ {
maxLen := 0
for j := i - 1; j >= 0; j-- {
if nums[i] > nums[j] {
maxLen = max(maxLen, dp[j])
}
}
dp[i] = maxLen + 1
ans = max(ans, dp[i])
}
return ans
}
1.3 最长公共子数组
// dp[i][j] 表示nums1[i-1] nums2[j-1]结尾的子数组的最大公共后缀长度
// 如果必须以nums1[i-1]结尾,那么找最长公共子数组 等价于 找最长公共后缀,因为子数组必须连续
// 相等 dp[i][j] = dp[i-1][j-1] + 1
// 不等 0
// 答案: max(dp[i][j])
func findLength(nums1 []int, nums2 []int) int {
m, n := len(nums1), len(nums2)
dp := make([][]int, m+1)
for i := range dp {
dp[i] = make([]int, n+1)
}
ans := 0
for i := 1; i <= m; i++ {
for j := 1; j <= n; j++ {
a, b := nums1[i-1], nums2[j-1]
if a == b {
dp[i][j] = dp[i-1][j-1] + 1
}
ans = max(ans, dp[i][j])
}
}
return ans
}
2. 状态机dp
dp[i][状态=j] 表示 a[0:i] 在 状态j 下的最优解。
2.1 买股票1
其实不是dp,也可以是dp,anyway,代码简单易懂。
// 维护左边的最小值
func maxProfit(prices []int) int {
lmin := prices[0]
ans := 0
for _, v := range prices {
ans = max(ans, v - lmin)
lmin = min(lmin, v)
}
return ans
}
2.2 买股票2
// dp[i][0] 表示 [0:i]天,不持有股票的最大利润
// dp[i][1] 表示 [0:i]天,持有股票的最大利润
// 不持有 = max(前一天不持有, 前一天持有 + 今天价格)
// 持有 = max(前一天持有, 前一天不持有 - 今天价格)
func maxProfit(prices []int) int {
n := len(prices)
dp := make([][2]int, n)
dp[0][0] = 0
dp[0][1] = -prices[0]
for i := 1; i < n; i++ {
dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i])
}
return dp[n-1][0]
}