DAY 38
1 2 3 4 5 6 7 8 9 10 11 12 func fib (n int ) int { if n == 0 || n == 1 { return n } dp := make ([]int , n+1 ) dp[0 ], dp[1 ] = 0 , 1 for i := 2 ; i <= n; i++ { dp[i] = dp[i-1 ] + dp[i-2 ] } return dp[n] }
1 2 3 4 5 6 7 8 9 10 11 func climbStairs (n int ) int { if n == 1 { return 1 } dp := make ([]int , n+1 ) dp[0 ], dp[1 ] = 1 , 1 for i := 2 ; i <= n; i++ { dp[i] += dp[i-1 ] + dp[i-2 ] } return dp[n] }
1 2 3 4 5 6 7 8 func minCostClimbingStairs (cost []int ) int { n := len (cost) dp := make ([]int , n+1 ) for i := 2 ; i <= n; i++ { dp[i] = min(dp[i-1 ]+cost[i-1 ], dp[i-2 ]+cost[i-2 ]) } return dp[n] }
DAY 39
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func uniquePaths (m int , n int ) int { dp := make ([][]int , m) for i := 0 ; i < m; i++ { dp[i] = make ([]int , n) } for i := 0 ; i < m; i++ { dp[i][0 ] = 1 } for j := 0 ; j < n; j++ { dp[0 ][j] = 1 } for i := 1 ; i < m; i++ { for j := 1 ; j < n; j++ { dp[i][j] = dp[i-1 ][j] + dp[i][j-1 ] } } return dp[m-1 ][n-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 func uniquePathsWithObstacles (obstacleGrid [][]int ) int { m, n := len (obstacleGrid), len (obstacleGrid[0 ]) dp := make ([][]int , m) for i := 0 ; i < m; i++ { dp[i] = make ([]int , n) } for i := 0 ; i < m; i++ { if obstacleGrid[i][0 ] == 1 { break } dp[i][0 ] = 1 } for j := 0 ; j < n; j++ { if obstacleGrid[0 ][j] == 1 { break } dp[0 ][j] = 1 } for i := 1 ; i < m; i++ { for j := 1 ; j < n; j++ { if obstacleGrid[i][j] == 1 { continue } dp[i][j] = dp[i-1 ][j] + dp[i][j-1 ] } } return dp[m-1 ][n-1 ] }
DAY 40
周日休息
DAY 41
定义 dp[i]
为拆分 i
的最大乘积,我们最后要求到 dp[n]
递推公式:dp[i]=max(dp[i],j*(i-j),j*dp[i-j])
1 2 3 4 5 6 7 8 9 10 func integerBreak (n int ) int { dp := make ([]int , n+1 ) dp[1 ], dp[2 ] = 1 , 1 for i := 3 ; i <= n; i++ { for j := 1 ; j < i+1 ; j++ { dp[i] = max(dp[i], max(j*(i-j), j*dp[i-j])) } } return dp[n] }
卡特兰数,dp[i]=sum(dp[j-1]*dp[i-j])
1 2 3 4 5 6 7 8 9 10 func numTrees (n int ) int { dp := make ([]int , n+1 ) dp[0 ] = 1 for i := 1 ; i <= n; i++ { for j := 1 ; j <= i; j++ { dp[i] += dp[j-1 ] * dp[i-j] } } return dp[n] }
DAY 42(背包问题)
朴素 01 背包
假设你有 N
个物品,每个物品有一个重量 w[i]
和一个价值 v[i]
。同时你有一个容量为 W
的背包。目标是在不超过背包容量的情况下,最大化装入背包的物品总价值
dp[i][j]
表示前 i
件物品(部分或全部)放入一个容量为 j
的背包可以获得的最大价值
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 func knapsack (weight, value []int , W, N int ) int { dp := make ([][]int , N) for i := 0 ; i < N; i++ { dp[i] = make ([]int , W+1 ) } for j := 0 ; j <= W; j++ { if j >= weight[0 ] { dp[0 ][j] = value[0 ] } } for i := 1 ; i < N; i++ { for j := 0 ; j <= W; j++ { if j < weight[i] { dp[i][j] = dp[i-1 ][j] } else { dp[i][j] = max(dp[i-1 ][j], dp[i-1 ][j-weight[i]]+value[i]) } } } return dp[N-1 ][W] }
滚动数组 01 背包
因为每次状态转移只依赖上一次的状态,所以可以使用滚动数组压缩到一维
记住这个一维的就好了,而且记住背包是逆序遍历,如果是正序就是完全背包了
1 2 3 4 5 6 7 8 9 func knapsack (weight, value []int , W, N int ) int { dp := make ([]int , W+1 ) for i := 0 ; i < N; i++ { for j := W; j >= weight[i]; j-- { dp[j] = max(dp[j], dp[j-weight[i]]+value[i]) } } return dp[W] }
变体之「恰好装满」
如果要求「恰好装满」则可以这样处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func knapsackExact(weight, value []int, W, N int) int { dp := make([]int, W+1) + for i := 1; i <= W; i++ { // 将 1...W 初始化为 -INF + dp[i] = math.MinInt + } for i := 0; i < N; i++ { // 所有物品 for j := W; j >= weight[i]; j-- { // 背包 W...weight[i] + if dp[j-weight[i]] != math.MinInt { // 越界检查,仅处理正常值 dp[j] = max(dp[j], dp[j-weight[i]]+value[i]) + } } } return dp[W] // 如果还是 -INF 则没法恰好装满 }
变体之「恰好装满方案个数」
这时 dp[i]
的含义就变成方案个数了,并且转移方程也变了,但是要牢记不变的遍历顺序
1 2 3 4 5 6 7 8 9 10 11 func knapsackExactCount(weight []int, W, N int) int { dp := make([]int, W+1) + dp[0] = 1 // 无物品时,恰好装满重量为0的方式有一种 for i := 0; i < N; i++ { // 所有物品 for j := W; j >= weight[i]; j-- { // 背包 W...weight[i] - dp[j] = max(dp[j], dp[j-weight[i]]+value[i]) + dp[j] += dp[j-weight[i]] } } return dp[W] // 返回恰好装满背包的方案数 }
变体之「多维背包」
假如物品不止重量一个限制条件,而是还有体积等其他维度条件,则为多维背包问题
每多一个维度就加一维 dp
和 for
就行,下面以二维举例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func knapsack2D(weight, volume, value []int, W, V, N int) int { - dp := make([]int, W+1) + dp := make([][]int, W+1) + for i := range dp { + dp[i] = make([]int, V+1) + } + for i := 0; i < N; i++ { // 所有物品 for j := W; j >= weight[i]; j-- { // 背包重量 W...weight[i] + for k := V; k >= volume[i]; k-- { // 背包体积 V...volume[i] - dp[j] = max(dp[j], dp[j-weight[i]]+value[i]) + dp[j][k] = max(dp[j][k], dp[j-weight[i]][k-volume[i]]+value[i]) + } } } + - return dp[W] + return dp[W][V] }
01 背包变体,从若干物品中选择一部分,只能使用一次,能不能「恰好」填满 sum/2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func canPartition (nums []int ) bool { sum := 0 for _, num := range nums { sum += num } if sum%2 != 0 { return false } target := sum / 2 return knapsackExact(nums, nums, target, len (nums)) != math.MinInt }
DAY 43(背包问题)
核心思想是将石头分为两堆,使得这两堆石头的总重量尽可能接近
可以转化为背包,选择一组物品,它们的总重量最大且不超过所有物品总重量的一半
这里石头重量也是既是重量又是价值
1 2 3 4 5 6 7 8 9 10 11 12 func lastStoneWeightII (stones []int ) int { sum := 0 for _, stone := range stones { sum += stone } target := sum / 2 return sum - 2 *knapsack(stones, stones, target, len (stones)) }
我一开始只能写出一个递推的版本,而且写的有问题,喂给 GPT 他给我改好了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func findTargetSumWays (nums []int , target int ) int { dp := make (map [int ]int ) dp[0 ] = 1 for _, num := range nums { next := make (map [int ]int ) for sum, count := range dp { next[sum+num] += count next[sum-num] += count } dp = next } return dp[target] }
dp 思路有点巧妙,分成正号集合和负号集合,因为 正+负=sum
并且 正-负=target
所以解出
正 = ( t a r g e t + s u m ) / 2 正=(target+sum)/2
正 = ( t a r g e t + s u m ) /2
于是问题就转化成背包问题,选取一些元素正好凑成 (target+sum)/2
的个数
而如果不能整除,那就是无解
1 2 3 4 5 6 7 8 9 10 11 12 func findTargetSumWays (nums []int , target int ) int { sum := 0 for _, num := range nums { sum += num } if (target+sum)%2 != 0 || target+sum < 0 { return 0 } return knapsackExactCount(nums, (target+sum)/2 , len (nums)) }
可以转化为二维背包问题,两个维度的费用是 0 和 1 的个数,每个字符串的价值都是 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 func findMaxForm (strs []string , m int , n int ) int { zeros := make ([]int , len (strs)) ones := make ([]int , len (strs)) value := make ([]int , len (strs)) for i, s := range strs { zeros[i], ones[i] = countZerosOnes(s) value[i] = 1 } return knapsack2D(zeros, ones, value, m, n, len (strs)) } func countZerosOnes (s string ) (int , int ) { zeros, ones := 0 , 0 for _, c := range s { if c == '0' { zeros++ } else { ones++ } } return zeros, ones }
DAY 44(背包问题)
完全背包
完全背包对比 01 背包的不同在于每种物品可以选择无限次,而不是只选择一次
完全背包的实现就是 01 背包的第二层遍历循序反一下
1 2 3 4 5 6 7 8 9 10 func completeKnapsack(weight, value []int, W, N int) int { dp := make([]int, W+1) for i := 0; i < N; i++ { // 所有物品 - for j := W; j >= weight[i]; j-- { // 背包 W...weight[i] + for j := weight[i]; j <= W; j++ { // 背包 weight[i]...W dp[j] = max(dp[j], dp[j-weight[i]]+value[i]) } } return dp[W] }
变体之「恰好装满」
「恰好装满」的改动与上面 01 的改动一样,只要求恰好装满都是这样改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func completeKnapsackExact(weight, value []int, W, N int) int { dp := make([]int, W+1) + for i := 1; i <= W; i++ { // 将 1...W 初始化为 -INF + dp[i] = math.MinInt + } for i := 0; i < N; i++ { // 所有物品 for j := weight[i]; j <= W; j++ { // 背包 weight[i]...W + if dp[j-weight[i]] != math.MinInt { // 越界检查,仅处理正常值 dp[j] = max(dp[j], dp[j-weight[i]]+value[i]) + } } } return dp[W] // 如果还是 -INF 则没法恰好装满 }
变体之「恰好装满个数」
改动也是相同的,在完全背包的 commit 上(内层循序反转),再 commit 恰好装满个数
1 2 3 4 5 6 7 8 9 10 11 12 func completeKnapsackExactCount(weight []int, W, N int) int { dp := make([]int, W+1) + dp[0] = 1 // 无物品时,恰好装满重量为0的方式有一种 for i := 0; i < N; i++ { // 所有物品 for j := weight[i]; j <= W; j++ { // 背包 weight[i]...W - dp[j] = max(dp[j], dp[j-weight[i]]+value[i]) + dp[j] += dp[j-weight[i]] } } return dp[W] // 返回恰好装满背包的方案数 }
变体之「恰好装满排列个数」
之前都是求组合数(物品顺序不同算作同一方案),现在变成了求排列数
不同之处就是内外两层翻转一下,先遍历背包,再遍历物品
但是背包是 weight[i]...W
,而翻到外面了 i
还没有定义,所以需要里面再用 if 判断一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func completeKnapsackExactCount2(weight []int, W, N int) int { dp := make([]int, W+1) dp[0] = 1 // 无物品时,恰好装满重量为0的方式有一种 - for i := 0; i < N; i++ { // 所有物品 - for j := weight[i]; j <= W; j++ { // 背包 weight[i]...W + for j := 0; j <= W; j++ { // 背包 weight[i]...W + for i := 0; i < N; i++ { // 所有物品 + if j >= weight[i] { dp[j] += dp[j-weight[i]] + } } } return dp[W] // 返回恰好装满背包的方案数 }
两个 buff 叠上去就行:完全背包 + 恰好装满求个数
1 2 3 func change (amount int , coins []int ) int { return completeKnapsackExactCount(coins,amount,len (coins)) }
叠三个 buff 🤣:完全背包 + 恰好装满求个数 + 排列计数
1 2 3 func combinationSum4 (nums []int , target int ) int { return completeKnapsackExactCount2(nums,target,len (nums)) }
DAY 45(背包问题)
用牛刀杀鸡,使用完全背包的思路做
每次你可以爬 1
或 2
个台阶 -> 两个物品
你有多少种不同的方法可以爬到楼顶 -> 完全背包,恰好装满,排列数
1 2 3 func climbStairs (n int ) int { return completeKnapsackExactCount2([]int {1 ,2 },n,2 ) }
Buff 超多:完全背包(内层反向),恰好装满(初始化 -INF),而且不是物品最多,而是求最少
最多是 max,那最少就是 min,但是恰好装满已经是 -INF 了怎么办,那就初始化成 +INF 🤣
本题求钱币最小个数,那么钱币有顺序和没有顺序都可以,都不影响钱币的最小个数
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 func coinChange (coins []int , amount int ) int { value := make ([]int , len (coins)) for i := range value { value[i] = 1 } ans := completeKnapsackExactMin(coins, value, amount, len (coins)) if ans == math.MaxInt { return -1 } return ans } func completeKnapsackExactMin (weight, value []int , W, N int ) int { dp := make ([]int , W+1 ) for i := 1 ; i <= W; i++ { dp[i] = math.MaxInt } for i := 0 ; i < N; i++ { for j := weight[i]; j <= W; j++ { if dp[j-weight[i]] != math.MaxInt { dp[j] = min(dp[j], dp[j-weight[i]]+value[i]) } } } return dp[W] }
使用纯背包的思路理解,物品就是一系列完全平方数 1,4,9,16...sqrt(n)
,背包大小是 n
,每个物品价值是 1
完全背包 + 恰好装满 + 物品最少,和上题的函数一样
1 2 3 4 5 6 7 8 9 10 func numSquares (n int ) int { m := int (math.Sqrt(float64 (n))) nums := make ([]int , 0 , m) value := make ([]int , 0 , m) for i := 1 ; i <= n; i++ { nums = append (nums, i*i) value = append (value, 1 ) } return completeKnapsackExactMin(nums, value, n, m) }
融合思路可以就可以写成下面这样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func numSquares (n int ) int { dp := make ([]int , n+1 ) for i := 1 ; i <= n; i++ { dp[i] = math.MaxInt } for i := 1 ; i <= n; i++ { num := i * i for j := num; j <= n; j++ { dp[j] = min(dp[j], dp[j-num]+1 ) } } return dp[n] }
DAY 46
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func wordBreak (s string , wordDict []string ) bool { n := len (s) dp := make ([]bool , n+1 ) exist := make (map [string ]bool ) for i := 0 ; i < len (wordDict); i++ { exist[wordDict[i]] = true } dp[0 ] = true for i := 0 ; i <= n; i++ { for j := 0 ; j < i; j++ { if dp[j] && exist[s[j:i]] == true { dp[i] = true break } } } return dp[n] }
DAY 47
休息
DAY 48(打家劫舍系列)
经典 dp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func rob (nums []int ) int { n := len (nums) if n == 1 { return nums[0 ] } dp := make ([]int , n) dp[0 ] = nums[0 ] dp[1 ] = max(nums[0 ], nums[1 ]) for i := 2 ; i < n; i++ { dp[i] = max(dp[i-1 ], dp[i-2 ]+nums[i]) } return dp[n-1 ] }
围成一圈的小技巧就是考虑掐头去尾两种情况
1 2 3 4 5 6 7 8 9 10 11 func rob (nums []int ) int { n:=len (nums) if n == 1 { return nums[0 ] }else if n ==0 { return 0 } return max(rob1(nums[1 :]),rob1(nums[:len (nums)-1 ])) }
变成了树形 dp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func rob (root *TreeNode) int { var dfs func (curr *TreeNode) []int dfs = func (curr *TreeNode) []int { if curr == nil { return []int {0 , 0 } } l := dfs(curr.Left) r := dfs(curr.Right) robCurr := curr.Val + l[0 ] + r[0 ] notRobCurr := max(l[0 ], l[1 ]) + max(r[0 ], r[1 ]) return []int {notRobCurr, robCurr} } ans := dfs(root) return max(ans[0 ], ans[1 ]) }
DAY 49(股票系列)
不求复杂度最低,只求条理最清晰
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func maxProfit (prices []int ) int { n := len (prices) dp := make ([][3 ]int , n) dp[0 ][0 ] = 0 dp[0 ][1 ] = -prices[0 ] dp[0 ][2 ] = 0 for i := 1 ; i < n; i++ { dp[i][0 ] = dp[i-1 ][0 ] dp[i][1 ] = max(dp[i-1 ][1 ], dp[i-1 ][0 ]-prices[i]) dp[i][2 ] = max(dp[i-1 ][2 ], dp[i-1 ][1 ]+prices[i]) } return dp[n-1 ][2 ] }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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 ] }
DAY 50(股票系列)
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 func maxProfit (prices []int ) int { n := len (prices) dp := make ([][5 ]int , n) dp[0 ][0 ] = 0 dp[0 ][1 ] = -prices[0 ] dp[0 ][2 ] = 0 dp[0 ][3 ] = -prices[0 ] dp[0 ][4 ] = 0 for i := 1 ; i < n; i++ { dp[i][0 ] = dp[i-1 ][0 ] dp[i][1 ] = max(dp[i-1 ][1 ], dp[i-1 ][0 ]-prices[i]) dp[i][2 ] = max(dp[i-1 ][2 ], dp[i-1 ][1 ]+prices[i]) dp[i][3 ] = max(dp[i-1 ][3 ], dp[i-1 ][2 ]-prices[i]) dp[i][4 ] = max(dp[i-1 ][4 ], dp[i-1 ][3 ]+prices[i]) } return dp[n-1 ][4 ] }
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 func maxProfit (k int , prices []int ) int { n := len (prices) dp := make ([][]int , n) for i := 0 ; i < n; i++ { dp[i] = make ([]int , 2 *k+1 ) } for i := 1 ; i < 2 *k; i += 2 { dp[0 ][i] = -prices[0 ] } for i := 1 ; i < n; i++ { dp[i][0 ] = dp[i-1 ][0 ] for j := 1 ; j <= 2 *k; j++ { if j%2 == 1 { dp[i][j] = max(dp[i-1 ][j], dp[i-1 ][j-1 ]-prices[i]) } else { dp[i][j] = max(dp[i-1 ][j], dp[i-1 ][j-1 ]+prices[i]) } } } return dp[n-1 ][2 *k] }
DAY 51(股票系列)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func maxProfit (prices []int , fee int ) int { n := len (prices) dp := make ([][2 ]int , n) dp[0 ][0 ] = 0 dp[0 ][1 ] = -prices[0 ] - fee 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]-fee) } return dp[n-1 ][0 ] }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func maxProfit (prices []int ) int { n := len (prices) dp := make ([][3 ]int , n) dp[0 ][0 ] = 0 dp[0 ][1 ] = -prices[0 ] dp[0 ][2 ] = 0 for i := 1 ; i < n; i++ { dp[i][0 ] = max(dp[i-1 ][0 ], dp[i-1 ][2 ]) dp[i][1 ] = max(dp[i-1 ][1 ], dp[i-1 ][0 ]-prices[i]) dp[i][2 ] = max(dp[i-1 ][2 ], dp[i-1 ][1 ]+prices[i]) } return max(dp[n-1 ][0 ], dp[n-1 ][2 ]) }
DAY 52
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func lengthOfLIS (nums []int ) int { n := len (nums) dp := make ([]int , n) for i := 0 ; i < n; i++ { dp[i] = 1 } for i := 0 ; i < n; i++ { for j := 0 ; j < i; j++ { if nums[j] < nums[i] { dp[i] = max(dp[i], dp[j]+1 ) } } } ans := 0 for i := 0 ; i < n; i++ { ans = max(ans, dp[i]) } return ans }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func findLengthOfLCIS (nums []int ) int { n := len (nums) dp := make ([]int , n) for i := 0 ; i < n; i++ { dp[i] = 1 } for i := 1 ; i < n; i++ { if nums[i-1 ] < nums[i] { dp[i] = max(dp[i], dp[i-1 ]+1 ) } } ans := 0 for i := 0 ; i < n; i++ { ans = max(ans, dp[i]) } return ans }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func findLength (nums1 []int , nums2 []int ) int { m, n := len (nums1), len (nums2) dp := make ([][]int , m+1 ) for i := 0 ; i <= m; i++ { dp[i] = make ([]int , n+1 ) } ans := 0 for i := 1 ; i <= m; i++ { for j := 1 ; j <= n; j++ { if nums1[i-1 ] == nums2[j-1 ] { dp[i][j] = dp[i-1 ][j-1 ] + 1 ans = max(ans, dp[i][j]) } } } return ans }
DAY 53
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func longestCommonSubsequence (text1 string , text2 string ) int { m, n := len (text1), len (text2) dp := make ([][]int , m+1 ) for i := 0 ; i <= m; i++ { dp[i] = make ([]int , n+1 ) } for i := 1 ; i <= m; i++ { for 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] }
一开始想不出来,看了题解发现与上一题一模一样(
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func maxUncrossedLines (nums1 []int , nums2 []int ) int { m, n := len (nums1), len (nums2) dp := make ([][]int , m+1 ) for i := 0 ; i <= m; i++ { dp[i] = make ([]int , n+1 ) } for i := 1 ; i <= m; i++ { for j := 1 ; j <= n; j++ { if nums1[i-1 ] == nums2[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] }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func maxSubArray (nums []int ) int { n := len (nums) dp := make ([]int , n) dp[0 ] = nums[0 ] ans := nums[0 ] for i := 1 ; i < n; i++ { dp[i] = max(dp[i-1 ]+nums[i], nums[i]) ans = max(ans, dp[i]) } return ans }
DAY 54
休息
DAY 55(编辑距离系列)
编辑距离青春版,是否能从 t 删除部分字符得到 s
dp[i][j]
:s[:i]
和 t[:j]
的最长公共子序列长度
1 2 3 4 当前字符相等: dp[i-1][j-1] + 1 当前字符不相等: 放弃t[j](删除):dp[i][j-1]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func isSubsequence (s string , t string ) bool { m, n := len (s), len (t) dp := make ([][]int , m+1 ) for i := 0 ; i <= m; i++ { dp[i] = make ([]int , n+1 ) } for i := 1 ; i <= m; i++ { for j := 1 ; j <= n; j++ { if s[i-1 ] == t[j-1 ] { dp[i][j] = dp[i-1 ][j-1 ] + 1 } else { dp[i][j] = dp[i][j-1 ] } } } return dp[m][n] == m }
dp[i][j]
:s[:i]
从出现 t[:j]
的个数
1 2 3 4 当前字符s[i-1]与t[j-1]相等: 使用s[i-1]+不使用s[i-1]:dp[i-1][j-1]+dp[i-1][j] 当前字符不相等: 不使用s[i-1]:dp[i-1][j]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 func numDistinct (s string , t string ) int { m, n := len (s), len (t) dp := make ([][]int , m+1 ) for i := 0 ; i <= m; i++ { dp[i] = make ([]int , n+1 ) } for i := 0 ; i <= m; i++ { dp[i][0 ] = 1 } for i := 1 ; i <= m; i++ { for j := 1 ; j <= n; j++ { if s[i-1 ] == t[j-1 ] { dp[i][j] = dp[i-1 ][j-1 ] + dp[i-1 ][j] } else { dp[i][j] = dp[i-1 ][j] } } } return dp[m][n] }
DAY 56
与下一题相同,但是少了一个改的操作
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 func minDistance (word1 string , word2 string ) int { n, m := len (word1), len (word2) dp := make ([][]int , n+1 ) for i := 0 ; i <= n; i++ { dp[i] = make ([]int , m+1 ) } for i := 0 ; i <= n; i++ { dp[i][0 ] = i } for j := 0 ; j <= m; j++ { dp[0 ][j] = j } for i := 1 ; i <= n; i++ { for j := 1 ; j <= m; 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 ]) + 1 } } } return dp[n][m] }
1 2 3 4 5 6 7 8 当前字符word1[i-1 ]与word2[j-1 ]相等: 不操作:dp[i-1 ][j-1 ] 当前字符不相等: min( word1删除一个元素:dp[i-1 ][j]+1 (等同于word2增加一个元素) word2删除一个元素:dp[i][j-1 ]+1 (等同于word1增加一个元素) 改:dp[i-1 ][j-1 ]+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 func minDistance (word1 string , word2 string ) int { n, m := len (word1), len (word2) dp := make ([][]int , n+1 ) for i := 0 ; i <= n; i++ { dp[i] = make ([]int , m+1 ) } for i := 0 ; i <= n; i++ { dp[i][0 ] = i } for j := 0 ; j <= m; j++ { dp[0 ][j] = j } for i := 1 ; i <= n; i++ { for j := 1 ; j <= m; 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[n][m] }
DAY 57
dp[i][j]
:s[i:j]
是回文子串
1 2 3 4 5 s[i] != s[j]: false s[i] == s[j]: abs(i-j)<=1 : true abs(i-j)>1 : dp[i+1 ][j-1 ]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func countSubstrings (s string ) int { n := len (s) dp := make ([][]bool , n) for i := 0 ; i < n; i++ { dp[i] = make ([]bool , n) } ans := 0 for i := n - 1 ; i >= 0 ; i-- { for j := i; j < n; j++ { if s[i] == s[j] { if j-i <= 1 || dp[i+1 ][j-1 ] { ans++ dp[i][j] = true } } } } return ans }
dp[i][j]
:s[i:j]
的最长回文子序列
1 2 3 4 s[i]==s[j]: dp[i+1 ][j-1 ]+2 s[i]!=s[j]: max(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 22 23 func longestPalindromeSubseq (s string ) int { n := len (s) dp := make ([][]int , n) for i := 0 ; i < n; i++ { dp[i] = make ([]int , n) } for i := 0 ; i < n; i++ { dp[i][i] = 1 } for i := n - 1 ; i >= 0 ; i-- { for j := i + 1 ; j < n; j++ { if s[i] == s[j] { dp[i][j] = dp[i+1 ][j-1 ] + 2 } else { dp[i][j] = max(dp[i+1 ][j], dp[i][j-1 ]) } } } return dp[0 ][n-1 ] }