算法笔试-动态规划

关于语言:一刷用 Golang,二刷用 Rust

题单来源于:分享丨【题单】动态规划(入门/背包/状态机/划分/区间/状压/数位/树形/数据结构优化)

题单

多数题目会使用记忆化搜索和递推两种方式。

入门 dp

爬楼梯模型:

题目 来源 难度 分数
70. 爬楼梯 简单
746. 使用最小花费爬楼梯 第 63 场周赛 Q1 简单 1358
377. 组合总和 Ⅳ 中等
2466. 统计构造好字符串的方案数 第 91 场双周赛 Q2 中等 1694
2266. 统计打字方案数 第 292 场周赛 Q3 中等 1857

打家劫舍模型,偷与不偷:

题目 来源 难度 分数
198. 打家劫舍 中等
740. 删除并获得点数 中等
2320. 统计放置房子的方式数 第 299 场周赛 Q2 中等 1608
213. 打家劫舍 II 中等

双序列 dp:一般定义 \(dp[i][j]\) 表示对子问题 \((s1[:i],s2[:j])\) 的求解结果

题目 来源 难度 分数
1143. 最长公共子序列 中等
1092. 最短公共超序列 第 141 场周赛 Q4 困难 1977
72. 编辑距离 中等

题解

70. 爬楼梯

func climbStairs(n int) int {
    memo := make([]int, n + 1)
    var dfs func(int) int
    dfs = func(x int) int {
        if x <= 1 {
            return 1
        }
        p := &memo[x]
        if *p == 0 {
            *p = dfs(x - 1) + dfs(x - 2)
        }
        return *p
    }
    return dfs(n)
}
func climbStairs(n int) int {
    f := make([]int, n + 1)
    f[0], f[1] = 1, 1
    for i := 2; i <= n; i++ {
        f[i] = f[i-1] + f[i-2]
    }
    return f[n]
}

746. 使用最小花费爬楼梯

func minCostClimbingStairs(cost []int) int {
    n := len(cost)
    memo := make([]int, n) 
    for i := range memo {
        memo[i] = -1
    }
    var dfs func(int) int
    dfs = func(x int) int {
        if x < 0 {
            return 0
        }
        p := &memo[x]
        if *p < 0 {
            *p = min(dfs(x - 1), dfs(x - 2)) + cost[x]
        }
        return *p
    }
    return min(dfs(n - 1), dfs(n - 2))
}
func minCostClimbingStairs(cost []int) int {
    n := len(cost)
    f := make([]int, n)
    f[0], f[1] = cost[0], cost[1]
    for i := 2; i < n; i++ {
        f[i] = min(f[i-1], f[i-2]) + cost[i]
    }
    return min(f[n-1], f[n-2])
}

377. 组合总和 Ⅳ

func combinationSum4(nums []int, target int) int {
    memo := make([]int, target + 1) 
    for i := range memo {
        memo[i] = -1
    }
    var dfs func(int) int
    dfs = func(i int) int {
        if i <= 0 {
            return 1
        }
        p := &memo[i]
        if *p < 0 {
            ans := 0
            for _, x := range nums {
                if x <= i {
                    ans += dfs(i - x)
                }
            }
            *p = ans
        }
        return *p
    }
    return dfs(target)
}
func combinationSum4(nums []int, target int) int {
    f := make([]int, target + 1)
    f[0] = 1
    for i := 1; i <= target; i++ {
        for _, x := range nums {
            if x <= i {
                f[i] += f[i-x]
            }
        }
    }
    return f[target]
}

2466. 统计构造好字符串的方案数

func countGoodStrings(low int, high int, zero int, one int) int {
    const mod int = 1e9 + 7
    memo := make([]int, high + 1)
    for i := range memo {
        memo[i] = -1
    }
    var dfs func(int) int
    dfs = func(i int) int {
        if i < 0 {
            return 0
        }
        if i == 0 {
            return 1
        }
        p := &memo[i]
        if *p < 0 {
            *p = (dfs(i - zero) + dfs(i - one)) % mod
        }
        return *p
    }

    ans := 0
    for i := low; i <= high; i++ {
        ans += dfs(i)
    }
    return ans % mod
}
func countGoodStrings(low int, high int, zero int, one int) int {
    const mod int = 1e9 + 7
    f := make([]int, high + 11)
    f[0] = 1
    for i := 1; i <= high; i++ {
        if i >= zero {
            f[i] = (f[i] + f[i-zero]) % mod
        }
        if i >= one {
            f[i] = (f[i] + f[i-one]) % mod
        }
    }
    ans := 0
    for i := low; i <= high; i++ {
        ans += f[i]
    }
    return ans % mod
}

2266. 统计打字方案数

func countTexts(pressedKeys string) int {
    const mod int = 1e9 + 7
    n := len(pressedKeys)
    memo := make([]int, n + 1) 
    for i := range memo {
        memo[i] = -1
    }
    var dfs func(int) int
    dfs = func(i int) int {
        if i < 0 {
            return 1
        }
        cnt, ans := 0, 0
        if pressedKeys[i] == '7' || pressedKeys[i] == '9' {
            cnt = 4
        } else {
            cnt = 3
        }
        p := &memo[i]
        if *p < 0 {
            for j := range cnt {
                if i < j || pressedKeys[i-j] != pressedKeys[i] {
                    break
                }
                ans += dfs(i - j - 1)
            }
            *p = ans % mod
        }
        return *p
    }
    return dfs(n - 1) % mod
}
func countTexts(pressedKeys string) int {
    const mod int = 1e9 + 7
    n := len(pressedKeys)
    f := make([]int, n + 1)
    f[0] = 1
    for i, v := range pressedKeys {
        cnt := 0
        if v == '7' || v == '9' {
            cnt = 4
        } else {
            cnt = 3
        }
        for j := 0; j < cnt; j++ {
            if i < j || pressedKeys[i-j] != pressedKeys[i] {
                break
            }
            f[i+1] = (f[i+1] + f[i-j]) % mod
        }
    }
    return f[n] % mod
}

198. 打家劫舍

func rob(nums []int) int {
    n := len(nums)
    memo := make([]int, n + 1) 
    for i := range memo {
        memo[i] = -1
    }
    var dfs func(int) int
    dfs = func(i int) int {
        if i < 0 {
            return 0
        }
        p := &memo[i]
        if *p < 0 {
            *p = max(dfs(i - 1) , dfs(i - 2) + nums[i])
        }
        return *p
    }
    return dfs(n - 1)
}
func rob(nums []int) int {
    n := len(nums)
    if n == 1 {
        return nums[0]
    }
    f := make([]int, n + 1) 
    f[0], f[1] = nums[0], max(nums[0], nums[1])
    for i := 2; i < n; i++ {
        f[i] = max(f[i-1], f[i-2] + nums[i])
    }
    return f[n-1]
}

740. 删除并获得点数

func deleteAndEarn(nums []int) int {
    cnt := [10010]int{}
    for _, v := range nums {
        cnt[v] += v
    }
    slices.Sort(nums)
    n := nums[len(nums)-1]
    memo := make([]int, n + 1)
    for i := range memo {
        memo[i] = -1
    }
    var dfs func(int) int
    dfs = func(i int) int {
        if i < 0 {
            return 0
        }
        p := &memo[i]
        if *p < 0 {
            *p = max(dfs(i - 1), dfs(i - 2) + cnt[i])
        }
        return *p
    }
    return dfs(n)
}

2320. 统计放置房子的方式数

func countHousePlacements(n int) int {
    const mod int = 1e9 + 7
    f := [10010]int{1, 2}
    for i := 2; i <= n; i++ {
        f[i] = (f[i-1] + f[i-2]) % mod
    }
    ans := (f[n] * f[n]) % mod
    return ans
}

213. 打家劫舍 II

func rob1(nums []int, start, end int) int { // [start,end) 左闭右开
    f0, f1 := 0, 0
    for i := start; i < end; i++ {
        f0, f1 = f1, max(f1, f0+nums[i])
    }
    return f1
}

func rob(nums []int) int {
    n := len(nums)
    return max(nums[0]+rob1(nums, 2, n-1), rob1(nums, 1, n))
}

1143. 最长公共子序列

\(f(i, j)\) 表示 \(text_1[0:i]\)\(text_2[0:j]\) 的最长公共子序列长度。

func longestCommonSubsequence(text1 string, text2 string) int {
    n, m := len(text1), len(text2)
    f := make([][]int, n + 1)
    for i := range f {
        f[i] = make([]int, m + 1)
    }

    for i, t1 := range text1 {
        for j, t2 := range text2 {
            if t1 == t2 {
                f[i+1][j+1] = f[i][j] + 1
            } else {
                f[i+1][j+1] = max(f[i][j+1], f[i+1][j])
            }
        }
    }
    return f[n][m]
}

72. 编辑距离

\(f(i, j-1)\) 相当于插入操作,\(f(i-1, j)\) 相当于删除 \(s\) 中的字符,\(f(i-1,j-1)\) 相当于替换操作。

func minDistance(word1 string, word2 string) int {
    n, m := len(word1), len(word2)
    f := make([][]int, n + 1)
    for i := range f {
        f[i] = make([]int, m + 1)
    }
    // 注意状态的初始化
    for i := 1; i <= m; i++ {
        f[0][i] = i
    }
    for i, w1 := range word1 {
        f[i+1][0] = i + 1
        for j, w2 := range word2 {
            if w1 == w2 {
                f[i+1][j+1] = f[i][j]
            } else {
                f[i+1][j+1] = min(min(f[i+1][j], f[i][j+1]), f[i][j]) + 1
            }
        }
    }
    return f[n][m]
}

1092. 最短公共超序列

func shortestCommonSupersequence(str1 string, str2 string) string {
    n, m := len(str1), len(str2)
    f := make([][]int, n + 1)
    for i := range f {
        f[i] = make([]int, m + 1)
        f[i][0] = i
    }
    for j := 1; j <= m; j++ {
        f[0][j] = j
    }

    for i, s1 := range str1 {
        for j, s2 := range str2 {
            f[i+1][j+1] = min(f[i+1][j], f[i][j+1]) + 1
            if s1 == s2 {
                f[i+1][j+1] = f[i][j] + 1
            }
        }
    }

    ans := []byte{}
    i, j := n - 1, m - 1
    for i >= 0 && j >= 0 {
        if str1[i] == str2[j] {
            ans = append(ans, str1[i])
            i--
            j--
        } else if f[i+1][j+1] == f[i][j+1] + 1 {
            ans = append(ans, str1[i])
            i--
        } else {
            ans = append(ans, str2[j])
            j--
        }
    }
    slices.Reverse(ans)
    return str1[:i+1] + str2[:j+1] + string(ans)
}