[Golang修仙之路] 算法专题:动态规划

38 阅读2分钟

写在前面:动态规划的题目和知识点简直浩如烟海,特别基础的有,特别难的也有,我这里只面向面试,整理一些经常出现的。

【一张思维导图】

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]
}