当前位置: 首页 > news >正文

动态规划DP问题详解,超全,思路全收集

1. 01背包问题 (01 Knapsack Problem)

问题描述:N 件物品和一个容量为 V 的背包。第 i 件物品的体积是 v[i],价值是 w[i]。求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。

  • 第一步:dp[i][j] 的定义是:
    dp[i][j] 表示从前 i 件物品中任意选择,放入容量为 j 的背包中所能获得的最大价值。

  • 第二步:为了达到 dp[i][j],最后一步可能是:
    这取决于我们是否选择第 i 件物品。

    1. 不放入第 i 件物品:那么 dp[i][j] 的值就等于将前 i-1 件物品放入容量为 j 的背包中的最大价值,即 dp[i-1][j]
    2. 放入第 i 件物品:前提是背包的容量 j 必须大于或等于第 i 件物品的体积 v[i]。此时的价值等于将前 i-1 件物品放入容量为 j - v[i] 的背包中的最大价值,再加上第 i 件物品的价值 w[i],即 dp[i-1][j - v[i]] + w[i]
  • 第三步:因此,状态转移方程是:
    dp[i][j] = max(dp[i-1][j], dp[i-1][j - v[i]] + w[i])(需要保证 j >= v[i])。

  • 第四步:初始条件是:
    dp[0][j] = 0,表示不选择任何物品时,总价值为0。
    dp[i][0] = 0,表示背包容量为0时,无法放入任何物品,总价值为0。

2. 最长上升子序列 (Longest Increasing Subsequence, LIS)

问题描述: 给定一个无序的整数数组,找到其中最长上升子序列的长度。

  • 第一步:dp[i] 的定义是:
    dp[i] 表示以数组中第 i 个元素 nums[i] 结尾的最长上升子序列的长度。

  • 第二步:为了达到 dp[i],最后一步可能是:
    nums[i] 是这个最长上升子序列的最后一个元素。它必然是接在前面某个元素 nums[j] (其中 j < inums[j] < nums[i] ) 之后构成的。 我们需要遍历所有在 i 之前的元素 j,找到一个能让 dp[i] 最大的 dp[j]

  • 第三步:因此,状态转移方程是:
    dp[i] = max(dp[j]) + 1,其中 0 <= j < inums[j] < nums[i]

  • 第四步:初始条件是:
    dp[i] = 1 对于所有的 i。因为每个元素自身都可以构成一个长度为1的上升子序列。

3. 最长公共子序列 (Longest Common Subsequence, LCS)

问题描述: 给定两个字符串 text1text2,返回这两个字符串的最长公共子序列的长度。

  • 第一步:dp[i][j] 的定义是:
    dp[i][j] 表示字符串 text1 的前 i 个字符(text1[0...i-1])与 text2 的前 j 个字符(text2[0...j-1])的最长公共子序列的长度。

  • 第二步:为了达到 dp[i][j],最后一步可能是:
    这取决于 text1 的第 i 个字符 (text1[i-1]) 和 text2 的第 j 个字符 (text2[j-1]) 是否相等。

    1. 如果 text1[i-1] == text2[j-1]:那么这个相等的字符必然在最长公共子序列中。此时的长度等于 text1 的前 i-1 个字符与 text2 的前 j-1 个字符的最长公共子序列长度加1,即 dp[i-1][j-1] + 1
    2. 如果 text1[i-1] != text2[j-1]:那么这两个字符至少有一个不在最长公共子序列中。此时的长度等于“text1 的前 i-1 个字符与 text2 的前 j 个字符的最长公共子序列”和“text1 的前 i 个字符与 text2 的前 j-1 个字符的最长公共子序列”中的较大者,即 max(dp[i-1][j], dp[i][j-1])
  • 第三步:因此,状态转移方程是:
    如果 text1[i-1] == text2[j-1],则 dp[i][j] = dp[i-1][j-1] + 1
    如果 text1[i-1] != text2[j-1],则 dp[i][j] = max(dp[i-1][j], dp[i][j-1])

  • 第四步:初始条件是:
    dp[0][j] = 0dp[i][0] = 0。表示任何字符串与空字符串的最长公共子序列的长度都为0。

4. 石子合并 (Stone Merging)

问题描述:N 堆石子排成一行,每堆石子有一定的质量。每次可以合并相邻的两堆石子,合并的代价为这两堆石子的质量之和。求将所有石子合并成一堆的最小总代价。

  • 第一步:dp[i][j] 的定义是:
    dp[i][j] 表示将从第 i 堆到第 j 堆的石子合并成一堆所需的最小代价。

  • 第二步:为了达到 dp[i][j],最后一步可能是:
    区间 [i, j] 内的石子最后一次合并,必然是由两个已经合并好的子区间 [i, k][k+1, j] 合并而成的,其中 i <= k < j

  • 第三步:因此,状态转移方程是:
    dp[i][j] = min(dp[i][k] + dp[k+1][j] + sum(i, j)),其中 i <= k < jsum(i, j) 表示第 i 堆到第 j 堆石子的总质量,也就是最后一次合并的代价。

  • 第四步:初始条件是:
    dp[i][i] = 0。因为单独一堆石子不需要合并,所以代价为0。

当然,我们继续按照这个模板来解析更多经典的动态规划问题。

5. 数字三角形 (Number Triangle)

问题描述: 在一个由数字组成的三角形中,从顶部出发,每次可以走到下一层的相邻两个节点中的一个,求一条从顶部到底部的路径,使得路径上的数字总和最大。

  • 第一步:dp[i][j] 的定义是:
    dp[i][j] 表示从三角形顶部(第一行)走到第 i 行第 j 个位置时,所能获得的最大路径和。

  • 第二步:为了达到 dp[i][j],最后一步可能是:
    要想到达第 i 行第 j 个位置,只能从上一层(第 i-1 行)的两个位置走过来:

    1. 从左上方的位置 (i-1, j-1) 走过来(如果 j-1 存在)。
    2. 从正上方的位置 (i-1, j) 走过来(如果 j 在上一行存在)。
  • 第三步:因此,状态转移方程是:
    dp[i][j] = max(dp[i-1][j-1], dp[i-1][j]) + triangle[i][j]。你需要处理边界情况,例如当 j=0 时只能从 dp[i-1][j] 过来,当 j 等于第 i 行的最后一个元素索引时,只能从 dp[i-1][j-1] 过来。

  • 第四步:初始条件是:
    dp[0][0] = triangle[0][0]。起点就是三角形顶部的那个数字。

(注意:这个问题也可以自底向上求解,此时 dp[i][j] 的定义会变成从 (i, j) 出发走到最底层的最大路径和,状态转移方程则如您列表中所示:f[i,j] = max(f[i+1,j], f[i+1,j+1]) + a[i,j])。

6. 乘积最大 (Maximum Product)

问题描述: 给定一个长度为 N 的数字字符串和 K 个乘号,要求将这 K 个乘号插入到数字串中,使得最终表达式的计算结果最大。

  • 第一步:dp[i][j] 的定义是:
    dp[i][j] 表示在数字串的前 i 个数字中,插入 j 个乘号所能获得的最大乘积。

  • 第二步:为了达到 dp[i][j],最后一步可能是:
    最后一个(第 j 个)乘号可以放在任意位置 k(其中 j <= k < i)的后面。这会将问题分为两部分:

    1. 在前 k 个数字中插入 j-1 个乘号,得到最大乘积 dp[k][j-1]
    2. 将第 k+1 到第 i 个数字组成的数作为最后一个乘数。
  • 第三步:因此,状态转移方程是:
    dp[i][j] = max(dp[k][j-1] * number(k+1, i)),其中 j <= k < inumber(k+1, i) 代表由第 k+1 到第 i 位数字组成的整数。

  • 第四步:初始条件是:
    dp[i][0] = number(1, i)。即不插入任何乘号时,最大乘积就是由前 i 个数字本身组成的整数。

7. Floyd-Warshall 算法

问题描述: 在一个加权有向图中,求任意两个顶点之间的最短路径长度。

  • 第一步:dp[k][i][j] 的定义是:
    dp[k][i][j] 表示从顶点 i 到顶点 j,只允许经过编号从 1k 的中间顶点时,所能得到的最短路径长度。

  • 第二步:为了达到 dp[k][i][j],最后一步可能是:
    从顶点 i 到顶点 j 且只经过 1...k 的最短路径,有两种可能:

    1. 不经过 顶点 k。那么这条路径的长度就是 dp[k-1][i][j]
    2. 经过 顶点 k。那么这条路径可以被分解为从 ik 的路径和从 kj 的路径,这两条子路径都只允许经过 1...k-1 的顶点。其长度为 dp[k-1][i][k] + dp[k-1][k][j]
  • 第三步:因此,状态转移方程是:
    dp[k][i][j] = min(dp[k-1][i][j], dp[k-1][i][k] + dp[k-1][k][j])
    (在实际实现中,k 这一维度可以被优化掉,方程简化为 dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]))。

  • 第四步:初始条件是:
    dp[0][i][j] 是图的邻接矩阵。如果 ij 之间有直接的边,则为该边的权重;如果 i == j,则为 0;否则为无穷大。

8. 编辑距离 (Edit Distance)

问题描述: 给定两个单词 word1word2,计算将 word1 转换成 word2 所使用的最少操作数。你可以对一个单词进行如下三种操作:插入一个字符、删除一个字符、替换一个字符。

  • 第一步:dp[i][j] 的定义是:
    dp[i][j] 表示将 word1 的前 i 个字符转换成 word2 的前 j 个字符所需要的最少操作数。
  • 第二步:为了达到 dp[i][j],最后一步可能是:
    考虑 word1[i-1]word2[j-1] 这两个字符:
    1. 如果 word1[i-1] == word2[j-1]:那么这个字符不需要操作,最少操作数等于将 word1 的前 i-1 个字符转换成 word2 的前 j-1 个字符的操作数,即 dp[i-1][j-1]
    2. 如果 word1[i-1] != word2[j-1]:我们有三种选择:
      • 替换:将 word1[i-1] 替换成 word2[j-1],操作数 = dp[i-1][j-1] + 1
      • 删除:删除 word1[i-1],然后将 word1 的前 i-1 个字符变成 word2 的前 j 个字符,操作数 = dp[i-1][j] + 1
      • 插入:在 word1 的前 i 个字符后插入 word2[j-1],这等价于将 word1 的前 i 个字符变成 word2 的前 j-1 个字符,操作数 = dp[i][j-1] + 1
        我们需要取这三种选择中的最小值。
  • 第三步:因此,状态转移方程是:
    如果 word1[i-1] == word2[j-1],则 dp[i][j] = dp[i-1][j-1]
    否则,dp[i][j] = 1 + min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1])
  • 第四步:初始条件是:
    dp[i][0] = i (将长度为 i 的字符串变为空串需要 i 次删除)。
    dp[0][j] = j (将空串变为长度为 j 的字符串需要 j 次插入)。

好的,我们继续。这里为您带来更多不同类型的经典动态规划问题,并同样遵循四步模板进行解析。

9. 爬楼梯 (Climbing Stairs)

问题描述: 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

  • 第一步:dp[i] 的定义是:
    dp[i] 表示爬到第 i 级台阶所拥有的不同方法的总数。

  • 第二步:为了达到 dp[i],最后一步可能是:
    要想到达第 i 级台阶,只有两种可能的方式:

    1. 从第 i-1 级台阶向上爬 1 步。
    2. 从第 i-2 级台阶向上爬 2 步。
      因此,到达第 i 级台阶的总方法数,就是到达第 i-1 级台阶的方法数与到达第 i-2 级台阶的方法数之和。
  • 第三步:因此,状态转移方程是:
    dp[i] = dp[i-1] + dp[i-2]

  • 第四步:初始条件是:
    dp[1] = 1 (到达第1级台阶只有一种方法:爬1步)。
    dp[2] = 2 (到达第2级台阶有两种方法:1+1 或 2)。

10. 打家劫舍 (House Robber)

问题描述: 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,但相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被闯入,系统会自动报警。给定一个代表每间房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,一夜之内能够偷窃到的最高金额。

  • 第一步:dp[i] 的定义是:
    dp[i] 表示在前 i 间房屋中(包括第 i 间),能够偷窃到的最高总金额。

  • 第二步:为了达到 dp[i],最后一步可能是:
    对于第 i 间房屋,你有两种选择:

    1. 不偷第 i 间房屋:那么你所能获得的最大金额就等于在前 i-1 间房屋中能偷到的最大金额,即 dp[i-1]
    2. 偷第 i 间房屋:由于不能偷相邻的房屋,所以你不能偷第 i-1 间。此时的最大金额等于在前 i-2 间房屋中能偷到的最大金额,再加上第 i 间房屋的金额,即 dp[i-2] + nums[i]
  • 第三步:因此,状态转移方程是:
    dp[i] = max(dp[i-1], dp[i-2] + nums[i])

  • 第四步:初始条件是:
    dp[0] = nums[0] (只有一间房时,只能偷这一间)。
    dp[1] = max(nums[0], nums[1]) (有两间房时,选择金额较大的那间偷)。

11. 零钱兑换 (Coin Change)

问题描述: 给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。你可以认为每种硬币的数量是无限的。

  • 第一步:dp[i] 的定义是:
    dp[i] 表示凑成总金额为 i 所需的最少硬币数量。

  • 第二步:为了达到 dp[i],最后一步可能是:
    要凑成总金额 i,我们的最后一枚硬币可以是 coins 数组中的任意一个面值 c (只要 i >= c)。如果我们选择了面值为 c 的硬币作为最后一枚,那么问题就变成了“凑成总金额为 i-c 所需的最少硬币数量”再加 1。我们需要遍历所有可能的最后一枚硬币 c,并找出能使 dp[i] 最小的那个选择。

  • 第三步:因此,状态转移方程是:
    dp[i] = min(dp[i - c]) + 1,其中 ccoins 数组中的一个面值,且 i >= c

  • 第四步:初始条件是:
    dp[0] = 0 (凑成金额 0 需要 0 个硬币)。
    数组的其他所有位置可以初始化为一个特殊值(例如,无穷大或 amount + 1),用来表示该金额当前还无法凑成。

12. 分割等和子集 (Partition Equal Subset Sum)

问题描述: 给定一个只包含正整数的非空数组 nums。判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

  • 这个问题可以转化为01背包问题:首先计算整个数组的总和 sum。如果 sum 是奇数,则不可能分割成两个和相等的子集,直接返回 false。如果 sum 是偶数,问题就变成了:能否从数组 nums 中找到一个子集,使得这个子集的和恰好等于 sum / 2
    这等价于:我们有一个容量为 sum / 2 的背包,和 N 个物品(nums 中的每个数都是一个物品),每个物品的体积和价值都等于其数值。问能否恰好装满这个背包。
  • 第一步:dp[j] 的定义是:
    dp[j] 是一个布尔值,表示背包容量为 j 时,能否被恰好装满(即 dp[j] = true)或不能(dp[j] = false)。
  • 第二步:为了达到 dp[j],最后一步可能是:
    对于数组中的每一个数字 num,我们在更新 dp 数组时考虑是否要将 num 放入背包。
    1. 不放入 num:那么 dp[j] 的状态不变。
    2. 放入 num:前提是 j >= num。如果 dp[j - num]true(即容量为 j - num 的背包能被装满),那么现在再放入 num,容量为 j 的背包也就能被装满了。
  • 第三步:因此,状态转移方程是:
    (使用一维数组优化后,从后向前遍历)
    dp[j] = dp[j] || dp[j - num]
  • 第四步:初始条件是:
    dp[0] = true (容量为0的背包可以被“空集”装满)。其余 dp[j] 初始化为 false

好的,我们继续深入探讨更多经典的动态规划模型,并严格按照您的四步模板进行解析。

13. 完全背包问题 (Unbounded Knapsack Problem)

问题描述:N 种物品和一个容量为 V 的背包,每种物品都有无限件可用。第 i 种物品的体积是 v[i],价值是 w[i]。求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。

  • 第一步:dp[j] 的定义是:
    dp[j] 表示容量为 j 的背包,所能装入物品的最大总价值。

  • 第二步:为了达到 dp[j],最后一步可能是:
    为了达到容量 j 的最大价值,我们可以考虑最后放入背包的是哪种物品。假设最后放入的是第 i 种物品,那么背包在此之前的状态是:容量为 j - v[i],并且已经达到了该容量下的最大价值 dp[j - v[i]]。然后我们再放入一个物品 i,总价值就变成了 dp[j - v[i]] + w[i]。我们需要遍历所有物品,找出能使这个总价值最大的选择。

  • 第三步:因此,状态转移方程是:
    dp[j] = max(dp[j], dp[j - v[i]] + w[i]) (对于所有物品 i,且 j >= v[i])。
    注意: 这与01背包的核心区别在于 dp[j] 是由 dp[j - v[i]] 推导而来,使用的是当前轮次的 dp 值。这允许了物品 i 被重复选择,从而体现了“无限件”的特性。

  • 第四步:初始条件是:
    dp[0] = 0。容量为0的背包无法装任何物品,价值为0。

14. 最长回文子序列 (Longest Palindromic Subsequence)

问题描述: 给定一个字符串 s,找到 s 中最长的回文子序列的长度。子序列不要求字符是连续的。

  • 第一步:dp[i][j] 的定义是:
    dp[i][j] 表示在字符串 s 的子串 s[i...j](从索引 ij)中,最长回文子序列的长度。

  • 第二步:为了达到 dp[i][j],最后一步可能是:
    这取决于子串 s[i...j] 两端的字符 s[i]s[j] 的关系:

    1. 如果 s[i] == s[j]:那么这两个字符可以作为回文序列的两端。最长长度就是 2 加上内部子串 s[i+1...j-1] 的最长回文子序列长度,即 dp[i+1][j-1] + 2
    2. 如果 s[i] != s[j]:那么 s[i]s[j] 不可能同时出现在最长回文子序列的两端。因此,我们需要分别放弃一个字符,看哪种情况能得到更长的结果。即比较“在 s[i+1...j] 中的最长回文子序列”和“在 s[i...j-1] 中的最长回文子序列”的长度,取较大者。也就是 max(dp[i+1][j], dp[i][j-1])
  • 第三步:因此,状态转移方程是:
    如果 s[i] == s[j],则 dp[i][j] = dp[i+1][j-1] + 2
    如果 s[i] != s[j],则 dp[i][j] = max(dp[i+1][j], dp[i][j-1])

  • 第四步:初始条件是:
    dp[i][i] = 1。对于任意 i,单个字符本身就是一个长度为1的回文子序列。

15. 单词拆分 (Word Break)

问题描述: 给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。

  • 第一步:dp[i] 的定义是:
    dp[i] 是一个布尔值,表示字符串 s 中长度为 i 的前缀 s[0...i-1] 是否可以被成功拆分。

  • 第二步:为了达到 dp[i],最后一步可能是:
    要判断 s[0...i-1] 能否被拆分,我们可以寻找一个分割点 j0 <= j < i)。如果 s[0...j-1] 本身可以被成功拆分(即 dp[j]true),并且从 ji-1 的子串 s[j...i-1] 恰好是字典中的一个单词,那么 dp[i] 就是 true。我们需要遍历所有可能的分割点 j

  • 第三步:因此,状态转移方程是:
    dp[i] = OR(dp[j] && s[j...i-1] in wordDict),对于所有 0 <= j < i。只要有一个 j 满足条件,dp[i] 就为 true

  • 第四步:初始条件是:
    dp[0] = true。一个空字符串(长度为0的前缀)可以被认为是一个有效的拆分,这是递推的起点。

16. 不同路径 (Unique Paths / 过河卒简化版)

问题描述: 一个机器人在一个 m x n 的网格的左上角(grid[0][0])。机器人每次只能向下或向右移动一步。机器人试图达到网格的右下角(grid[m-1][n-1])。问总共有多少条不同的路径?

  • 第一步:dp[i][j] 的定义是:
    dp[i][j] 表示从起点 (0,0) 到达网格中位置 (i,j) 的不同路径的总数。
  • 第二步:为了达到 dp[i][j],最后一步可能是:
    由于机器人只能向下或向右移动,要想到达位置 (i,j),它必须从紧邻的上方或左方格子过来:
    1. 从上方格子 (i-1, j) 向下移动一步。
    2. 从左方格子 (i, j-1) 向右移动一步。
      因此,到达 (i,j) 的总路径数等于到达 (i-1,j) 的路径数加上到达 (i,j-1) 的路径数。
  • 第三步:因此,状态转移方程是:
    dp[i][j] = dp[i-1][j] + dp[i][j-1]
  • 第四步:初始条件是:
    dp[0][j] = 1 对于所有 j,并且 dp[i][0] = 1 对于所有 i。这是因为对于网格的第一行,机器人只能一直向右走,路径只有1条;同理,对于第一列,只能一直向下走,路径也只有1条。dp[0][0] 自然是1。

当然可以,我们继续为您解析更多具有代表性的动态规划问题。

17. 合唱队形 (Chorus Formation)

问题描述: N 位同学站成一排,音乐老师要请其中的 (N-K) 位同学出列,使得剩下的 K 位同学排成合唱队形。合唱队形是指这样的一种队形:设 K 位同学从左到右依次为 1, 2, ..., K,他们的身高分别为 T1, T2, ..., TK,则他们的身高满足 T1 < T2 < ... < Ti > Ti+1 > ... > TK (1 <= i <= K)。你的任务是,已知所有 N 位同学的身高,计算最少需要几位同学出列,才能使得剩下的同学排成合唱队形。

  • 这个问题可以分解为两次最长上升子序列(LIS)的计算。 核心思想是枚举每一个同学作为合唱队形的“最高点”,然后计算以他为最高点时,能形成的最长合唱队形有多长。

  • 第一步:dp 数组的定义是:

    1. left_dp[i]:以第 i 位同学为结尾的最长上升子序列的长度(从左向右看)。
    2. right_dp[i]:以第 i 位同学为开头的最长下降子序列的长度(从左向右看),这等价于从右向左看,以第 i 位同学为结尾的最长上升子序列的长度。
  • 第二步:为了达到 dp[i],最后一步可能是:

    1. 对于 left_dp[i]:第 i 位同学可以接在任何他左边(j < i)且比他矮(height[j] < height[i])的同学 j 之后,形成一个更长的上升序列。
    2. 对于 right_dp[i]:同理,可以从右边找到比他矮的同学来构造。
    3. 一个以第 i 位同学为顶点的合唱队形,其总人数为 left_dp[i](左边的上升部分)加上 right_dp[i](右边的下降部分),但因为第 i 位同学被计算了两次,所以需要减 1。
  • 第三步:因此,状态转移方程是:

    1. left_dp[i] = max(left_dp[j]) + 1,其中 0 <= j < iheight[j] < height[i]
    2. right_dp[i] = max(right_dp[j]) + 1,其中 i < j < Nheight[j] < height[i]
    3. 最终合唱队形的最大长度 max_len = max(left_dp[i] + right_dp[i] - 1),遍历所有 i
    4. 最少出列人数 = N - max_len
  • 第四步:初始条件是:
    left_dp[i] = 1right_dp[i] = 1 对所有 i 成立。因为每个同学自己都能构成一个长度为1的序列。

18. 数的划分 (Integer Partition)

问题描述: 将整数 n 划分成 k 份,且每份不能为空,任意两份的顺序不被区分(例如 1,1,51,5,1 被认为是同一种划分)。求总共有多少种不同的划分方法。

  • 第一步:dp[i][j] 的定义是:
    dp[i][j] 表示将整数 i 划分成 j 份的总方法数。

  • 第二步:为了达到 dp[i][j],最后一步可能是:
    对于 ij 份划分,可以根据这 j 份中是否含有 1 来分类:

    1. 划分中不包含 1:这意味着每一份都至少是 2。我们可以从每一份中都拿走一个 1,总共拿走 j1,剩下的整数是 i-j,它需要被划分为 j 份。这种情况下的方法数等于将 i-j 划分为 j 份的方法数,即 dp[i-j][j]
    2. 划分中至少包含一个 1:我们可以先拿走一个 1,剩下的整数是 i-1,它需要被划分为 j-1 份。这种情况下的方法数等于将 i-1 划分为 j-1 份的方法数,即 dp[i-1][j-1]
  • 第三步:因此,状态转移方程是:
    dp[i][j] = dp[i-j][j] + dp[i-1][j-1]

  • 第四步:初始条件是:
    dp[k][k] = 1 (将 k 划分为 k 份,只有 1,1,...,1 一种方法)。
    dp[i][1] = 1 (将 i 划分为 1 份,只有 i 本身这一种方法)。
    i < j 时, dp[i][j] = 0 (无法将一个小数划分成更多份)。

19. 多边形剖分 (Polygon Triangulation)

问题描述: 在一个凸 n 边形的顶点上,赋有 n 个权值。要求用 n-3 条不相交的对角线将该凸 n 边形剖分成 n-2 个三角形,并使得这些三角形的顶点权值乘积之和最小。

  • 这是一个典型的区间DP问题,与石子合并非常相似。

  • 第一步:dp[i][j] 的定义是:
    dp[i][j] 表示由顶点 v_i, v_{i+1}, ..., v_j 构成的子多边形进行三角剖分的最小代价(顶点按顺序编号)。

  • 第二步:为了达到 dp[i][j],最后一步可能是:
    考虑连接顶点 v_iv_j 的边,它必然是某个三角形的一条边。这个三角形的第三个顶点必然是 v_k,其中 i < k < j。这条对角线 (v_i, v_k)(v_k, v_j) 将原问题 dp[i][j] 分割成了两个子问题 dp[i][k]dp[k][j],以及一个新形成的三角形 (v_i, v_k, v_j) 的代价。

  • 第三步:因此,状态转移方程是:
    dp[i][j] = min(dp[i][k] + dp[k][j] + w[i]*w[k]*w[j]),需要遍历所有可能的中间点 ki < k < j)。

  • 第四步:初始条件是:
    dp[i][i+1] = 0。因为由相邻两个顶点构成的“多边形”(即一条边)无法再剖分,代价为0。

20. 买卖股票的最佳时机 (Best Time to Buy and Sell Stock - multiple variations)

我们以一个常见的变种为例:最多可完成两笔交易

问题描述: 给定一个数组 prices,它的第 i 个元素是给定股票在第 i 天的价格。设计一个算法来计算你所能获取的最大利润。你最多可以完成两笔交易。注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

  • 第一步:dp[i][k][s] 的定义是:
    dp[i][k][s] 表示在第 i 天结束时,已经进行了 k 笔交易,并且手中持有状态为 s 时所能获得的最大利润。
    • i:天数 (from 0 to n-1)
    • k:已完成的交易次数 (0, 1, or 2)
    • s:持有状态 (0 for no stock, 1 for has stock)
  • 第二步:为了达到 dp[i][k][s],最后一步可能是:
    1. 为了达到 dp[i][k][0] (第i天,k笔交易,不持股):
      • 可能是前一天就不持股,今天什么都不做(rest)。利润继承自 dp[i-1][k][0]
      • 可能是今天卖出了股票。这笔卖出操作完成了第 k 笔交易。那么利润就是前一天持有股票(dp[i-1][k][1])的基础上加上今天的卖出价 prices[i]
    2. 为了达到 dp[i][k][1] (第i天,k笔交易,持股):
      • 可能是前一天就持股,今天什么都不做(rest)。利润继承自 dp[i-1][k][1]
      • 可能是今天买入了股票。这次买入是第 k 笔交易的开始。那么利润就是前一天不持股且已完成 k-1 笔交易(dp[i-1][k-1][0])的基础上减去今天的买入价 prices[i]
  • 第三步:因此,状态转移方程是:
    dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
    dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])
  • 第四步:初始条件是:
    dp[-1][k][0] = 0 (开始前利润为0)
    dp[-1][k][1] = -infinity (开始前不可能持有股票,设为负无穷防止被错误选择)
    dp[i][0][1] = -infinity (不允许交易时不可能持有股票)

好的,我们继续探索动态规划的世界,这里是更多经典问题的模板化解析。

21. 树形DP - 没有上司的舞会

问题描述: 某公司要举办一场晚会,共 N 名员工,构成一个树形的上下级关系(除了老板,每个人都有且只有一个直属上司)。如果邀请了某位员工,那么为了避免尴尬,就不能邀请他的直属上司。每位员工参加晚会都会带来一定的“快乐指数”。求邀请哪些员工可以使得晚会的总快乐指数最大。

  • 第一步:dp[u][s] 的定义是:
    dp[u][0] 表示在以员工 u 为根的子树中(即 u 和他的所有下属),在 不邀请 员工 u 的情况下,能获得的最大快乐指数。
    dp[u][1] 表示在以员工 u 为根的子树中,在 邀请 员工 u 的情况下,能获得的最大快乐指数。

  • 第二步:为了达到 dp[u][s],最后一步可能是:
    状态 dp[u] 的值取决于其所有直接下属 v 的状态。

    1. 如果不邀请 u (dp[u][0]):那么对于 u 的每一个直接下属 v,我们既可以邀请他,也可以不邀请他。为了使快乐指数最大,我们应该选择这两种情况中能带来更大快乐值的那一种,即 max(dp[v][0], dp[v][1])。我们需要将 u 的所有直接下属的这个最大值累加起来。
    2. 如果邀请 u (dp[u][1]):那么 u 的所有直接下属 v 都不能被邀请。所以我们只能累加在不邀请 v 的情况下的最大快乐指数,即 dp[v][0]。最终还要加上 u 本身的快乐指数。
  • 第三步:因此,状态转移方程是:
    (遍历 u 的所有直接下属 v
    dp[u][0] = sum(max(dp[v][0], dp[v][1]))
    dp[u][1] = happiness[u] + sum(dp[v][0])

  • 第四步:初始条件是:
    对于一个叶子节点(没有下属的员工)l
    dp[l][0] = 0 (不邀请他,快乐指数增加0)。
    dp[l][1] = happiness[l] (邀请他,快乐指数就是他自己的值)。
    计算通常使用深度优先搜索(DFS),从叶子节点开始向上计算,直到根节点。

22. 状态压缩DP - 旅行商问题 (TSP)

问题描述: 给定 N 个城市和每对城市之间的距离,求解访问每一座城市一次并回到起始城市的最短回路距离。

  • 第一步:dp[S][j] 的定义是:
    dp[S][j] 表示已经访问过的城市集合为 SS 是一个用二进制位表示的集合,例如 1101 表示访问了第0, 2, 3号城市),且当前停留在城市 j 时,所经过的最短路径总长度。

  • 第二步:为了达到 dp[S][j],最后一步可能是:
    要想到达“访问了集合 S 并停在 j”这个状态,上一步必然是处于“访问了集合 S 中除去 j 的所有城市,并停在某个城市 k”的状态,其中 k 属于 Sk != j。然后从城市 k 直接移动到城市 j

  • 第三步:因此,状态转移方程是:
    dp[S][j] = min(dp[S XOR (1 << j)][k] + distance(k, j))
    这个方程的含义是:遍历集合 S 中除了 j 以外的所有城市 k,找出那个能使“从起点经过 S-{j} 到达 k,再从 kj”的路径最短的 k

  • 第四步:初始条件是:
    假设从城市 0 出发。
    dp[1][0] = 0 (只访问了城市0,当前停在城市0,路径长度为0)。
    所有其他的 dp[S][j] 初始化为无穷大。

23. 多重背包问题

问题描述:N 种物品和一个容量为 V 的背包。第 i 种物品最多有 c[i] 件,每件体积是 v[i],价值是 w[i]。求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。

  • 第一步:dp[i][j] 的定义是:
    dp[i][j] 表示将前 i 种物品放入容量为 j 的背包中,可以获得的最大价值。

  • 第二步:为了达到 dp[i][j],最后一步可能是:
    对于第 i 种物品,我们可以选择放入 k 件,其中 0 <= k <= c[i]。如果我们决定放入 k 件,那么问题就转化为:将前 i-1 种物品放入容量为 j - k * v[i] 的背包中,能获得的最大价值,再加上我们这 k 件物品的价值 k * w[i]

  • 第三步:因此,状态转移方程是:
    dp[i][j] = max(dp[i-1][j - k * v[i]] + k * w[i]),需要遍历所有可能的 k0 <= k <= c[i]j >= k * v[i])。
    (注:这个问题通常通过“二进制优化”将多重背包转化为01背包问题来高效求解,但以上是其最朴素的状态转移思想。)

  • 第四步:初始条件是:
    dp[0][j] = 0。不考虑任何物品时,价值为0。

24. 矩阵链乘法 (Matrix Chain Multiplication)

问题描述: 给定 n 个矩阵的序列 <A1, A2, ..., An>,计算它们的乘积。由于矩阵乘法满足结合律,我们可以任意加括号决定计算顺序。不同的计算顺序可能导致截然不同的计算量(总的标量乘法次数)。求一个最优的加括号方案,使得总的标量乘法次数最少。

  • 这是一个经典的区间DP问题,与石子合并、多边形剖分同源。
  • 第一步:dp[i][j] 的定义是:
    dp[i][j] 表示计算矩阵序列 AiAj 的乘积 Ai...j 所需的最小标量乘法次数。
  • 第二步:为了达到 dp[i][j],最后一步可能是:
    为了计算 Ai...j,最后一步一定是将两个已经计算好的子矩阵链相乘。这个分割点可以在 k 处,其中 i <= k < j。也就是说,我们先计算出 Ai...k 的结果,再计算出 Ak+1...j 的结果,最后将这两个结果矩阵相乘。
  • 第三步:因此,状态转移方程是:
    dp[i][j] = min(dp[i][k] + dp[k+1][j] + p[i-1]*p[k]*p[j]),需要遍历所有可能的分割点 ki <= k < j)。
    其中 p 数组是矩阵的维度,矩阵 Am 的维度是 p[m-1] x p[m]p[i-1]*p[k]*p[j] 是将 (p[i-1] x p[k]) 的矩阵和 (p[k] x p[j]) 的矩阵相乘的计算代价。
  • 第四步:初始条件是:
    dp[i][i] = 0。单个矩阵不需要任何乘法,计算代价为0。

当然,这里有更多不同领域的经典动态规划问题,它们展示了DP思想的广泛应用。

25. 最小路径和 (Minimum Path Sum)

问题描述: 给定一个包含非负整数的 m x n 网格 grid,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。每次只能向下或者向右移动一步。

  • 第一步:dp[i][j] 的定义是:
    dp[i][j] 表示从左上角起点 (0,0) 到达网格中位置 (i,j) 的最小路径和。

  • 第二步:为了达到 dp[i][j],最后一步可能是:
    要想到达位置 (i,j),必然是从其上方格子 (i-1, j) 或者左方格子 (i, j-1) 移动过来的。为了使得到达 (i,j) 的路径和最小,我们应该选择从这两条路径中和较小的那一条走过来。

  • 第三步:因此,状态转移方程是:
    dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]

  • 第四步:初始条件是:
    dp[0][0] = grid[0][0]
    对于第一行:dp[0][j] = dp[0][j-1] + grid[0][j],因为只能从左边过来。
    对于第一列:dp[i][0] = dp[i-1][0] + grid[i][0],因为只能从上边过来。

26. 目标和 (Target Sum)

问题描述: 给定一个非负整数数组 nums 和一个目标数 S。现在你有 +- 两种符号。对于数组中的任意一个整数,你都可以从 +- 中选择一个符号添加在前面。返回可以使最终数组和为目标数 S 的所有添加符号的方法数。

  • 这个问题可以转化为一个0/1背包的计数问题。 假设给一部分数字 P 加上 + 号,给另一部分数字 N 加上 - 号。我们有 sum(P) - sum(N) = S。同时,sum(P) + sum(N) = total_sum(数组总和)。两个式子相加得到 2 * sum(P) = S + total_sum,所以 sum(P) = (S + total_sum) / 2。问题就变成了:从 nums 数组中找出若干个数字,使得它们的和恰好等于 (S + total_sum) / 2。有多少种找法?

  • 第一步:dp[j] 的定义是:
    dp[j] 表示从数组中选取若干个数,使其和恰好为 j 的方法总数。

  • 第二步:为了达到 dp[j],最后一步可能是:
    对于数组中的每一个数字 num,我们可以选择将它加入或者不加入构成和为 j 的集合。

    1. 不加入 num:那么构成和为 j 的方法数保持不变。
    2. 加入 num:那么我们需要在考虑 num 之前,就已经构成了和为 j - num。所以,方法数增加了 dp[j - num] 种。
  • 第三步:因此,状态转移方程是:
    (遍历 nums 中的每个 num,并从后向前更新 dp 数组)
    dp[j] = dp[j] + dp[j - num]

  • 第四步:初始条件是:
    dp[0] = 1。和为0有一种方法,就是不选择任何数(空集)。

27. 最长公共子串 (Longest Common Substring)

问题描述: 给定两个字符串 str1str2,找到两个字符串中连续且相等的最长子串的长度。

  • 这与最长公共子序列(LCS)不同,子串要求是连续的。

  • 第一步:dp[i][j] 的定义是:
    dp[i][j] 表示以 str1 的第 i 个字符(str1[i-1])和 str2 的第 j 个字符(str2[j-1]为结尾 的最长公共子串的长度。

  • 第二步:为了达到 dp[i][j],最后一步可能是:
    我们只关心 str1[i-1]str2[j-1] 是否相等。

    1. 如果 str1[i-1] == str2[j-1]:那么这两个字符可以延续它们前一个字符构成的公共子串。所以,长度就是以 str1[i-2]str2[j-2] 为结尾的最长公共子串长度加 1。
    2. 如果 str1[i-1] != str2[j-1]:那么公共子串在这里就断开了。以这两个字符为结尾的公共子串长度为 0。
  • 第三步:因此,状态转移方程是:
    如果 str1[i-1] == str2[j-1],则 dp[i][j] = dp[i-1][j-1] + 1
    如果 str1[i-1] != str2[j-1],则 dp[i][j] = 0
    最终的答案是整个 dp 表中的最大值,而不是 dp[m][n]

  • 第四步:初始条件是:
    dp[0][j] = 0dp[i][0] = 0。与空串的公共子串长度为0。

28. 石子游戏 (Stone Game)

问题描述: 有一排 N 堆石子,piles[i] 是每堆石子的数量。两名玩家轮流取石子,每次可以从行的开头或结尾取走整堆石子。取走石子总数最多的玩家获胜。假设两名玩家都采取最佳策略,判断先手玩家是否能获胜。

  • 这是一个典型的博弈型DP。
  • 第一步:dp[i][j] 的定义是:
    dp[i][j] 表示在只剩下从 ij 堆石子的情况下,当前轮到的玩家(先手)相对于另一位玩家(后手),能 拿到的最大石子数量。
  • 第二步:为了达到 dp[i][j],最后一步可能是:
    当前玩家面对 piles[i...j],他有两种选择:
    1. 取走 piles[i]:他获得 piles[i] 的石子。然后,对于剩下的 piles[i+1...j],他的对手会成为先手,并采取最优策略,多拿 dp[i+1][j] 的石子。因此,当前玩家最终多拿的石子数是 piles[i] - dp[i+1][j]
    2. 取走 piles[j]:他获得 piles[j] 的石子。然后,对于剩下的 piles[i...j-1],他的对手会成为先手,多拿 dp[i][j-1] 的石子。因此,当前玩家最终多拿的石子数是 piles[j] - dp[i][j-1]
      当前玩家会选择对自己最有利的,即能让他多拿更多石子的那种选择。
  • 第三步:因此,状态转移方程是:
    dp[i][j] = max(piles[i] - dp[i+1][j], piles[j] - dp[i][j-1])
  • 第四步:初始条件是:
    i == j 时,只剩下一堆石子。当前玩家会把它全部拿走。
    dp[i][i] = piles[i]

好的,我们继续。动态规划的世界非常广阔,这里还有一些涵盖了不同思想和技巧的经典问题。

29. 最大子数组和 (Maximum Subarray)

问题描述: 给定一个整数数组 nums,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

  • 第一步:dp[i] 的定义是:
    dp[i] 表示以数组中第 i 个元素 nums[i] 为结尾 的所有连续子数组中,和最大的那个子数组的和。

  • 第二步:为了达到 dp[i],最后一步可能是:
    对于以 nums[i] 结尾的最大和子数组,只有两种可能:

    1. 这个子数组只包含 nums[i] 本身。
    2. 这个子数组包含了 nums[i] 以及它前面的元素。在这种情况下,它必然是“以 nums[i-1] 结尾的最大和子数组”再加上 nums[i] 构成的。
  • 第三步:因此,状态转移方程是:
    dp[i] = max(nums[i], dp[i-1] + nums[i])
    最终的结果是整个 dp 数组中的最大值,因为最大子数组不一定在数组末尾结束。

  • 第四步:初始条件是:
    dp[0] = nums[0]。以第一个元素结尾的最大子数组和就是它本身。

30. 戳气球 (Burst Balloons)

问题描述:n 个气球,编号为 0n-1,每个气球上都有一个数字,这些数字存在数组 nums 中。现在要求你戳破所有的气球。每当你戳破一个气球 i 时,你可以获得 nums[left] * nums[i] * nums[right] 个硬币。这里的 leftright 代表和 i 相邻的两个气球的序号。当一个气球被戳破后,它左右两边的气球就变成了相邻的气球。求所能获得硬币的最大数量。
(可以假设 nums[-1] = nums[n] = 1,但它们不能被戳破)。

  • 这是一个非常经典的区间DP,但需要逆向思维。 正向思考(先戳哪个)会导致后续状态不独立。我们反过来想:最后戳破哪个气球。

  • 第一步:dp[i][j] 的定义是:
    dp[i][j] 表示戳破开区间 (i, j) 内所有气球,所能获得的最大硬币数量。为了方便处理边界,我们可以在原 nums 数组两边各加上一个 1

  • 第二步:为了达到 dp[i][j],最后一步可能是:
    我们考虑在区间 (i, j) 中,最后一个被戳破 的气球是 ki < k < j)。当我们戳破 k 时,它左右两边相邻的气球一定是 ij(因为 (i,k)(k,j) 之间的气球都已经被戳破了)。戳破 k 的收益是 nums[i] * nums[k] * nums[j]。而在此之前,我们需要先戳破区间 (i, k)(k, j) 的所有气球,这两部分是独立的子问题,其最大收益分别是 dp[i][k]dp[k][j]

  • 第三步:因此,状态转移方程是:
    dp[i][j] = max(dp[i][k] + nums[i] * nums[k] * nums[j] + dp[k][j]),需要遍历所有可能的 ki < k < j)。

  • 第四步:初始条件是:
    dp[i][i+1] = 0。当区间长度为0或1时(即 j = i+1),开区间 (i, i+1) 内没有气球,所以收益为0。

31. 交错字符串 (Interleaving String)

问题描述: 给定三个字符串 s1, s2, s3,请你帮忙验证 s3 是否是由 s1s2 交错组成的。交错的定义是:如果 s3 中可以找到一种划分,使得 s1s2 的所有字符都恰好出现一次,并且保持了它们在原字符串中的相对顺序。

  • 第一步:dp[i][j] 的定义是:
    dp[i][j] 是一个布尔值,表示 s1 的前 i 个字符和 s2 的前 j 个字符,能否成功交错组成 s3 的前 i+j 个字符。

  • 第二步:为了达到 dp[i][j],最后一步可能是:
    为了构成 s3 的前 i+j 个字符,s3 的最后一个字符 s3[i+j-1] 必然是来自 s1 的最后一个字符 s1[i-1] 或者是 s2 的最后一个字符 s2[j-1]

    1. 如果 s3[i+j-1] 等于 s1[i-1]:我们需要看 s1 的前 i-1 个字符和 s2 的前 j 个字符能否构成 s3 的前 i+j-1 个字符,即 dp[i-1][j] 是否为 true
    2. 如果 s3[i+j-1] 等于 s2[j-1]:我们需要看 s1 的前 i 个字符和 s2 的前 j-1 个字符能否构成 s3 的前 i+j-1 个字符,即 dp[i][j-1] 是否为 true
  • 第三步:因此,状态转移方程是:
    dp[i][j] = (dp[i-1][j] && s1[i-1] == s3[i+j-1]) || (dp[i][j-1] && s2[j-1] == s3[i+j-1])

  • 第四步:初始条件是:
    dp[0][0] = true (两个空字符串可以交错组成一个空字符串)。
    对于第一行:dp[0][j] = dp[0][j-1] && (s2[j-1] == s3[j-1])
    对于第一列:dp[i][0] = dp[i-1][0] && (s1[i-1] == s3[i-1])

32. 最长有效括号 (Longest Valid Parentheses)

问题描述: 给你一个只包含 '('')' 的字符串,找出最长有效(格式正确且连续)括号子串的长度。

  • 第一步:dp[i] 的定义是:
    dp[i] 表示以第 i 个字符 s[i] 为结尾 的最长有效括号子串的长度。
  • 第二步:为了达到 dp[i],最后一步可能是:
    1. 如果 s[i]'(':以开括号结尾的子串不可能是有效的,所以 dp[i] = 0
    2. 如果 s[i]')':我们需要寻找一个与之匹配的 '('
      • 情况一:s[i-1]'('。 那么 s[i-1]s[i] 构成了一对 ()。这个长度为2的有效串可以和 i-2 处结尾的有效串连接起来。所以 dp[i] = dp[i-2] + 2
      • 情况二:s[i-1]')'。 这意味着 s[i] 的匹配括号可能在更前面。如果 i-1 结尾是一个长度为 dp[i-1] 的有效串,那么我们去看这个有效串之前的位置 j = i - dp[i-1] - 1 是否是 '('。如果是,那么这个 '(' 就和 s[i] 匹配上了。此时的长度等于 dp[i-1] (中间的有效串) + 2 (新匹配的一对) + dp[j-1] (前面可能还连着一个有效串)。
  • 第三步:因此,状态转移方程是:
    如果 s[i] == ')'
    如果 s[i-1] == '(',则 dp[i] = dp[i-2] + 2
    如果 s[i-1] == ')'s[i - dp[i-1] - 1] == '(',则 dp[i] = dp[i-1] + 2 + dp[i - dp[i-1] - 2]
    (注意处理数组越界的情况)。
    最终答案是整个 dp 数组中的最大值。
  • 第四步:初始条件是:
    dp 数组全部初始化为 0

好的,我们继续。这里有另一组经典的动态规划问题,它们分别代表了不同的DP子类型和思考角度。

33. 完全平方数 (Perfect Squares)

问题描述: 给定一个正整数 n,找到若干个完全平方数(例如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。

  • 这是一个完全背包问题的变体。 物品是所有小于等于 n 的完全平方数,物品的“体积”和“价值”都是1,背包容量是 n

  • 第一步:dp[i] 的定义是:
    dp[i] 表示和为 i 的最少完全平方数的个数。

  • 第二步:为了达到 dp[i],最后一步可能是:
    为了凑成和 i,我们添加的最后一个数必然是一个完全平方数,比如 j*j。那么,在这之前,我们必须已经凑成了和为 i - j*j。我们需要遍历所有可能的最后一个完全平方数 j*j,并从中找到能使总数最小的方案。

  • 第三步:因此,状态转移方程是:
    dp[i] = min(dp[i - j*j]) + 1,其中 j*j <= i

  • 第四步:初始条件是:
    dp[0] = 0。和为0需要0个完全平方数。

34. 解码方法 (Decode Ways)

问题描述: 一条包含字母 A-Z 的消息通过以下方式进行了编码:'A' -> "1", 'B' -> "2", ..., 'Z' -> "26"。给定一个只包含数字的非空字符串 s,请计算解码方法的总数。

  • 第一步:dp[i] 的定义是:
    dp[i] 表示字符串 s 的前 i 个字符(s[0...i-1])所拥有的解码方法总数。

  • 第二步:为了达到 dp[i],最后一步可能是:
    解码到第 i 个字符时,我们关注最后的一位或两位数字是如何解码的。

    1. 解码最后一位:如果 s 的第 i 个字符 s[i-1] 不是 '0',那么它可以被独立解码。这种情况下,解码方法数继承自前 i-1 个字符的解码方法数,即 dp[i-1]
    2. 解码最后两位:如果 s 的第 i-1 和第 i 个字符 s[i-2...i-1] 组成的两位数在 "10" 到 "26" 之间,那么它们可以一起被解码。这种情况下,解码方法数继承自前 i-2 个字符的解码方法数,即 dp[i-2]
      总方法数是这两种独立情况的和。
  • 第三步:因此,状态转移方程是:
    dp[i] = (符合条件的 dp[i-1]) + (符合条件的 dp[i-2])
    具体来说:
    如果 s[i-1] 在 '1'~'9' 之间,则 dp[i] += dp[i-1]
    如果 s[i-2...i-1] 在 "10"~"26" 之间,则 dp[i] += dp[i-2]

  • 第四步:初始条件是:
    dp[0] = 1。这表示一个空字符串有一种解码方式(即什么都不做),这是为了让递推在 i=1i=2 时能够正确进行。

35. 最大正方形 (Maximal Square)

问题描述: 在一个由 '0' 和 '1' 组成的二维矩阵内,找到只包含 '1' 的最大正方形,并返回其面积。

  • 第一步:dp[i][j] 的定义是:
    dp[i][j] 表示以矩阵中 (i-1, j-1) 位置的元素为 右下角 的最大正方形的 边长

  • 第二步:为了达到 dp[i][j],最后一步可能是:
    一个以 (i-1, j-1) 为右下角的正方形要存在,首先 matrix[i-1][j-1] 必须为 '1'。其次,它的左边、上边、左上角三个方向必须也都能形成正方形。这个新正方形的边长,受限于它左、上、左上三个方向的邻居所能形成的正方形的最小边长。例如,如果左、上、左上三个位置形成的正方形边长分别是 a, b, c,那么以当前位置为右下角能形成的最大正方形边长就是 min(a, b, c) + 1

  • 第三步:因此,状态转移方程是:
    如果 matrix[i-1][j-1] == '1',则 dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1
    如果 matrix[i-1][j-1] == '0',则 dp[i][j] = 0
    最终答案是整个 dp 表中最大值的平方。

  • 第四步:初始条件是:
    dp 数组的第一行和第一列可以直接根据 matrix 的值来初始化。如果 matrix[i-1][0] == '1',则 dp[i][0] = 1,反之亦然。

36. 最佳买卖股票时机含冷冻期 (Best Time to Buy and Sell Stock with Cooldown)

问题描述: 给定一个整数数组 prices,其中 prices[i] 表示第 i 天的股票价格。设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易:卖出股票后,你无法在第二天买入股票 (即存在1天的冷冻期)。

  • 第一步:dp 数组的定义是:
    由于存在冷冻期,我们需要定义多个状态。
    dp[i][0]:第 i 天结束时,手中 持有 股票的情况下,至今为止的最大利润。
    dp[i][1]:第 i 天结束时,手中 不持有 股票,且 处于冷冻期 的情况下,至今为止的最大利润。
    dp[i][2]:第 i 天结束时,手中 不持有 股票,且 不处于冷冻期 的情况下,至今为止的最大利润。

  • 第二步:为了达到 dp[i] 的各个状态,最后一步可能是:

    1. 为了达到 dp[i][0] (持有):可能是前一天就持有(dp[i-1][0]),今天休息;也可能是前一天不处于冷冻期(dp[i-1][2]),今天买入。
    2. 为了达到 dp[i][1] (不持有, 冷冻期):必然是前一天持有股票(dp[i-1][0]),今天卖出。
    3. 为了达到 dp[i][2] (不持有, 非冷冻期):可能是前一天就不处于冷冻期(dp[i-1][2]),今天休息;也可能是前一天处于冷冻期(dp[i-1][1]),今天冷冻期结束。
  • 第三步:因此,状态转移方程是:
    dp[i][0] = max(dp[i-1][0], dp[i-1][2] - prices[i])
    dp[i][1] = dp[i-1][0] + prices[i]
    dp[i][2] = max(dp[i-1][2], dp[i-1][1])
    最终的答案是 max(dp[n-1][1], dp[n-1][2]),因为最后一天肯定是不持有股票利润才最大。

  • 第四步:初始条件是:
    dp[0][0] = -prices[0] (第0天买入)
    dp[0][1] = 0 (不可能,但设为0方便计算)
    dp[0][2] = 0 (第0天什么都不做)

当然,动态规划的宝库还远未穷尽。这里有另一组代表性的、技巧性更强的经典动态规划问题,同样遵循您的四步模板。

37. 正则表达式匹配 (Regular Expression Matching)

问题描述: 给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 .* 的正则表达式匹配。. 匹配任意单个字符,* 匹配零个或多个前面的那一个元素。所谓匹配,是要涵盖整个输入字符串 s 的,而不是部分字符串。

  • 第一步:dp[i][j] 的定义是:
    dp[i][j] 是一个布尔值,表示字符串 s 的前 i 个字符 s[0...i-1] 是否能被模式 p 的前 j 个字符 p[0...j-1] 成功匹配。

  • 第二步:为了达到 dp[i][j],最后一步可能是:
    这取决于模式的最后一个字符 p[j-1] 是什么。

    1. 如果 p[j-1] 不是 *:那么 p[j-1] 必须匹配 s[i-1]p[j-1]. 或者等于 s[i-1])。要使 dp[i][j] 为真,前提是 dp[i-1][j-1] 也必须为真。
    2. 如果 p[j-1]** 会作用于它前面的字符 p[j-2]。这里有两种情况:
      • * 匹配零次:我们直接忽略 p[j-2]* 这部分,看 s 的前 i 个字符是否能被 p 的前 j-2 个字符匹配,即看 dp[i][j-2] 是否为真。
      • * 匹配一次或多次:前提是 s[i-1] 必须能被 p[j-2] 匹配。如果可以,那么 p[0...j]s[0...i] 的匹配结果就取决于 p[0...j]s[0...i-1] 的匹配结果,即看 dp[i-1][j] 是否为真。(相当于 * 帮助 p[j-2] 吃掉了 s[i-1],然后继续用 * 去尝试匹配 s 的前 i-1 个字符)。
  • 第三步:因此,状态转移方程是:
    如果 p[j-1] 不是 *dp[i][j] = i > 0 && dp[i-1][j-1] && (s[i-1] == p[j-1] || p[j-1] == '.')
    如果 p[j-1]*dp[i][j] = dp[i][j-2] || (i > 0 && dp[i-1][j] && (s[i-1] == p[j-2] || p[j-2] == '.'))

  • 第四步:初始条件是:
    dp[0][0] = true (空字符串可以被空模式匹配)。
    dp[i][0] = false (非空字符串无法被空模式匹配)。
    dp[0][j] 的情况需要特殊处理,因为像 a*a*b* 这样的模式可以匹配空字符串。所以 dp[0][j] = j > 1 && p[j-1] == '*' && dp[0][j-2]

38. 俄罗斯套娃信封 (Russian Doll Envelopes)

问题描述: 给定一些标记了宽度和高度的信封,宽度和高度都为正整数。当另一个信封的宽度和高度都比这个信封大的时候,这个信封就可以放进另一个信封里。请计算最多能有多少个信封能组成一组“俄罗斯套娃”信封(即一个可以套一个)。

  • 这是一个二维的最长上升子序列问题。 核心思想是通过巧妙的排序,将二维问题降维成一维的LIS问题。
    预处理步骤: 首先,将所有信封按照宽度 w 进行升序排序。如果宽度相同,则按照高度 h 进行 降序 排序。
    这样做的目的是:宽度相同时,高度更高的信封排在前面,保证了在后续求高度的LIS时,宽度相同的信封不可能被选入同一个上升子序列。

  • 第一步:dp[i] 的定义是:
    在经过排序后的信封数组的 高度 序列上,dp[i] 表示长度为 i 的最长上升子序列的 末尾元素 的最小值。这是一个经典的、使用贪心+二分法优化LIS的 dp 定义。

  • 第二步:为了达到 dp[i],最后一步可能是:
    我们遍历排序后的每一个信封的高度 h。对于每个 h,我们要在 dp 数组中找到一个合适的位置来更新它。

    1. 如果 hdp 数组中所有已知上升子序列的末尾元素都大,说明它可以接在最长的那个子序列后面,形成一个更长的子序列。
    2. 如果 h 不比所有末尾元素都大,我们就找到 dp 数组中第一个大于等于 h 的元素,并用 h 替换它。这表示我们找到了一个潜在的、结尾更小(因此更有潜力)的同样长度的上升子序列。
  • 第三步:因此,状态转移方程是:
    这不是一个传统的 dp[i] = ... 形式,而是通过二分查找来维护 dp 数组的过程。
    len 为当前最长上升子序列的长度。
    对每个高度 h
    binary_searchdp[1...len] 中找到 h 的位置。
    如果 h 能插入到 len 之后,则 len++, dp[len] = h
    否则,在 dp 中找到第一个大于等于 h 的位置 j,更新 dp[j] = h
    最终答案就是 len

  • 第四步:初始条件是:
    dp 数组为空,len = 0

39. 矩阵中的最长递增路径 (Longest Increasing Path in a Matrix)

问题描述: 给定一个整数矩阵 matrix,找出其中最长递增路径的长度。对于每个单元格,你可以往上、下、左、右四个方向移动。你不能在对角线方向上移动或移动到边界外。

  • 这是一个在图(由矩阵隐式定义)上寻找最长路径的问题,适合使用记忆化搜索(自顶向下的DP)。

  • 第一步:dp[i][j] 的定义是:
    dp[i][j] 表示从矩阵中单元格 (i, j) 出发,所能走出的最长递增路径的长度。

  • 第二步:为了达到 dp[i][j],最后一步可能是:
    (i, j) 出发,我们可以移动到其四周(上、下、左、右)的邻居 (ni, nj),只要邻居的值 matrix[ni][nj] 大于当前值 matrix[i][j]。从 (i, j) 出发的路径长度,就是 1 加上从那个符合条件的邻居出发的最长递增路径长度。我们需要在所有可能的下一步中选择能使总长度最长的那一个。

  • 第三步:因此,状态转移方程是:
    dp[i][j] = 1 + max(dp[ni][nj]),其中 (ni, nj)(i, j) 的有效邻居,且 matrix[ni][nj] > matrix[i][j]。如果没有这样的邻居,则 max 部分为0。

  • 第四步:初始条件是:
    dp 数组所有元素初始化为 0-1,表示尚未计算。这是一个递归的终止条件。当 dp[i][j] 不为 0 时,直接返回其值,避免重复计算。最终答案是整个 dp 表中的最大值。

40. 数字DP (Digit DP)

问题描述: (这是一个问题类型的泛称)计算在区间 [L, R] 内,满足特定属性的整数有多少个。例如,计算 [1, N] 中不包含 "49" 这个子串的数字个数。
通用解法: 通常将问题 count(R) - count(L-1) 转化为求解 [1, N] 范围内满足条件的数的个数。

  • 第一步:dp(pos, state, is_limit, is_leading_zero) 的定义是:
    这是一个记忆化搜索的函数。它返回从第 pos 位开始构造数字,在之前的状态为 state 的情况下,总共有多少种合法的构造方法。

    • pos: 当前正在处理的数字位(从高位到低位)。
    • state: 一个或多个状态变量,用于记录构造到目前为止的关键信息。例如,前一位数字是什么(为了判断 "49"),是否已经满足了某个条件等。
    • is_limit: 一个布尔值,表示当前位是否受到 N 对应位的限制。如果是,当前位数字最大只能取到 N[pos]
    • is_leading_zero: 一个布尔值,表示当前是否处于前导零阶段。
  • 第二步:为了达到 dp(pos, ...),最后一步可能是:
    在当前 pos 位,我们可以尝试填入数字 dd 的取值范围是 [0, up],其中 upis_limit 决定(如果 is_limit 为真,up = N[pos];否则 up = 9)。对于每一个合法的 d(即满足题目条件的 d,例如不能与前一位组成 "49"),我们递归地调用函数去解决子问题:dp(pos+1, new_state, new_is_limit, ...)

  • 第三步:因此,状态转移方程是:
    这是一个递归求和的形式:
    ans = 0
    for d from 0 to up:
    if d is valid:
    ans += dp(pos+1, new_state, is_limit && (d == up), ...)
    memo[pos][state] = ans (在不 is_limit 的情况下缓存结果)
    return ans

  • 第四步:初始条件是:
    递归的基准情况(出口):当 pos 超出 N 的长度时,说明我们成功构造了一个完整的、合法的数字,此时应该返回 1。整个过程由初始调用 dp(0, initial_state, true, true) 启动。

相关文章:

动态规划DP问题详解,超全,思路全收集

1. 01背包问题 (01 Knapsack Problem) 问题描述: 有 N 件物品和一个容量为 V 的背包。第 i 件物品的体积是 v[i],价值是 w[i]。求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。第一步:dp[i][j] 的定义是: dp[i][j] 表示从前 i 件物品中任意选…...

SQL入门与实战

SQL:用于访问和处理数据库的计算机语言...

day05 课程

day05 课程课程:https://www.bilibili.com/video/BV1o4411M71o?spm_id_from=333.788.videopod.episodes&p=94 5.1 学习字符串的必要性5.2 认识字符串5.3 字符串输出5.4 字符串输入5.5 下标5.6 切片简介5.7 体验切片5.8 字符串常用方法简介5.9 字符串常用操作方法之查找5.…...

【JPCS独立出版Fellow杰青云集】2025年先进材料与航空航天结构力学国际学术会议(AMASM 2025)

会议将聚焦“先进材料”、“结构力学”、“航空航天工程”等前沿领域,旨在为来自国内外高校、科研机构、企事业单位的专家、教授、学者、工程师等搭建一个交流最新研究成果、分享专业经验、拓展学术与产业网络的国际平台。大会将深入探讨本领域面临的关键技术挑战与发展方向,…...

算法-TSP旅行商问题-03 - jack

目录问题的核心要素问题的复杂性常见的解法对于大规模问题:穷举动态规划实现 (Held-Karp) 旅行商问题 (Traveling Salesperson Problem, TSP) 旅行商问题是计算机科学和运筹学领域一个非常经典且著名的组合优化问题, 给定一系列城市和每对城市之间的距离,找到一条最短的哈密…...

AI编程⑤:【Cursor保姆级教程】零基础小白从安装到实战,手把手教你玩转AI编程神器!

一、什么是AI编程? 以前的编程是靠专业技术人员+学习至少一门擅长的编程语言去写程序 现在是通过对话聊天+AI大模型写程序 对话+AI大模型=AI编程 所有的模型对话上下文都有长度限制,这也是目前AI编程一个短板所在 二、Cursor免费和收费的区别,怎么充值?模式特点免费只能使用…...

ArkTS

装饰器: 用于装饰类、结构、方法以及变量,并赋予其特殊的含义。如上述示例中@Entry、@Component和@State都是装饰器,@Component表示自定义组件,@Entry表示该自定义组件为入口组件,@State表示组件中的状态变量,状态变量变化会触发UI刷新。 UI描述:以声明式的方式来描述UI…...

一文读懂基因检测PLM、体外诊断试剂PLM的功能、价值、解决方案

在体外诊断(IVD)与基因检测行业,研发面临 “高合规门槛、高数据复杂度、高协同需求” 三重挑战:合规需满足 ISO 13485、FDA 21 CFR Part 11、NMPA 等多标准,疏漏易致上市延期;基因试剂需记录上百种物料参数,实验数据关联复杂,传统 Excel 管理易出错、难追溯;研发与 SA…...

ai本地部署工具有哪些?新手入门AI推荐这几个

随着ai技术的火热,越来越多新手想尝试在本地部署ai模型,体验ai的魅力。但面对复杂的部署流程,不少人望而却步。其实,选对工具能让部署变得简单。今天就给大家推荐几款适合新手的ai本地部署工具,其中首推DS本地部署大师,还会详细介绍用它部署DeepSeek的操作步骤。 一、几款…...

匿名内部类

...

文件上传、分片上传结合antdProComponents表格展示,点击上传

文件上传、分片上传结合antdProComponents表格展示,点击上传上传组件:// ChunkUpload.jsx import React, { useCallback, forwardRef, useState, useImperativeHandle, useEffect, useRef } from react; import { UploadDropZone } from @rpldy/upload-drop-zone; import {Ch…...

2025 年 PLM 市场新锐崛起:五家厂商以创新技术引领行业变革新路径

在制造业数字化转型的汹涌浪潮中,产品生命周期管理(PLM)领域正历经着前所未有的深刻变革。往昔传统的软件模式逐渐式微,一批将技术创新奉为圭臬的新锐厂商强势登场。它们凭借独具差异化的解决方案,宛如矫健的黑马,在市场中迅速崭露头角,为制造企业精心打造出从研发设计的…...

2025 年国产 PLM 系统发展全景:厂商实力与核心功能深度解读

随着国产 PLM 技术的持续突破,本土厂商在功能适配性、行业针对性和服务响应速度上的优势愈发凸显,为不同规模、不同领域的制造企业提供了更贴合需求的解决方案。本文将聚焦 2025 年国产 PLM 系统主流厂商,全面解析厂商特色与系统核心功能模块,为企业选型提供参考。一、2025…...

开发效率翻倍!编码助手+云效 AI 评审如何破解代码质量与速度难题?

如今随着 AI 技术的突破,这一问题出现了全新解法:使用编码助手(包括不限于通义灵码、Qoder、Cursor、Claude Code 等工具,本文以通义灵码作为示例) + 云效 AI 评审,助力解决传统开发流程中的一些挑战。作者:致信 背景 随着软件开发复杂度的持续攀升和产品迭代周期的不断…...

SSL部署完成,https显示连接不安全如何处理?

在部署 SSL 后,如果浏览器仍然显示 “连接不安全” 或 “Not Secure”,通常是由以下几种原因导致的。针对每种可能的原因和问题,以下提供了详细的排查和解决方案。1. 排查问题的可能原因 1.1 SSL 证书未正确安装 如果 SSL 证书安装不完整或配置错误,浏览器会显示连接不安全…...

各省简称

各省简称目录一、华北地区二、东北地区三、华东地区四、华中地区五、华南地区六、西南地区七、西北地区八、特别行政区记忆小技巧: 一、华北地区省份全称 简称 由来简述北京市 京 直接取自全称中的字。历史上是京城的所在地。天津市 津 直接取自全称中的字。意为“天子的渡口”…...

完整教程:HDFS基准测试与数据治理

完整教程:HDFS基准测试与数据治理pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", monospace !important; f…...

var code = 76cb2b4f-5a26-4a70-a3bf-dc8f2ae5162f

var code = "76cb2b4f-5a26-4a70-a3bf-dc8f2ae5162f"...

解放双手!三端通用的鼠标连点神器

前言在日常工作和游戏中,我们经常遇到需要大量重复点击的情况——无论是抢购、快速通过游戏关卡,还是处理繁琐的数据录入工作。手动重复点击不仅枯燥乏味,还容易导致手腕疲劳。今天给大家分享一款轻巧易用的鼠标连点器工具,支持多种点击模式,彻底解放你的双手!为什么推荐…...

用 C# 与 Tesseract 实现验证码识别系统

一、项目概述 验证码识别在自动化测试、爬虫开发与用户辅助系统中具有重要价值。本文将介绍如何使用 C# 调用 Tesseract OCR 实现验证码图像识别功能,并对验证码图像进行简单预处理,以提高识别准确率。 二、开发环境准备安装 Tesseract 更多内容访问ttocr.com或联系143642394…...

【9月19日最终截稿,SPIE出版】2025年信息工程、智能信息技术与人工智能国际学术会议(IEITAI 2025)

2025年信息工程、智能信息技术与人工智能国际学术会议(IEITAI 2025)将于2025年9月26-28日在黑龙江哈尔滨盛大召开。旨在为全球学者、工程师及行业专家提供一个高水平交流平台,围绕信息工程、人工智能、大数据、物联网、5G/6G通信等前沿领域展开研讨,分享最新研究成果与技术…...

Dockerfile:如何用CMD同时启动两个进程

场景 在一个Dockerfile中,如何编写CMD指令,使得可以同时启动两个进程? 方案 这两个进程假设分别为Springboot Jar工程、sh脚本:app.jar script.sh需要明确一点:CMD指令本身只能直接执行一个命令 所以我们只能通过间接方式来做到启动多个进程:使用启动脚本start.sh,在其中…...

启动GA-Event Activated,结束GA-End Ability

在GA中 Event Activated是激活时的行为 在激活结尾时调用End Ability...

202003_MRCTF_千层套娃

ZIP套娃,QR二维码Tags:ZIP套娃,QRCODE 0x00. 题目 附件路径:https://pan.baidu.com/s/1GyH7kitkMYywGC9YJeQLJA?pwd=Zmxh#list/path=/CTF附件 附件名称:202003_MRCTF_千层套娃.zip 0x01. WP 01. 打开压缩文件发现hint信息 发现是zip套娃,需要通过python脚本进行自动化解压H…...

基于MATLAB的粒子群算法优化广义回归神经网络的实现

基于MATLAB的粒子群算法(PSO)优化广义回归神经网络(GRNN)的实现一、算法原理与流程 graph TDA[数据准备] --> B[PSO参数初始化]B --> C[GRNN适应度计算]C --> D[粒子速度更新]D --> E[粒子位置更新]E --> F[全局最优解更新]F --> G[GRNN模型训练]G -->…...

MySql EXPLAIN 详解

1、EXPLAIN介绍 EXPLAIN语句提供MySQL如何执行语句的信息。EXPLAIN返回SELECT语句中使用的每个表的信息并列出一行运行数据。它是按照MySQL在处理语句时读取表的顺序列出并输出到一个表格中。2、查询示例 2.1、【explain + 表名】显示的是这个表的表结构。 2.2、【explain + s…...

Transformer完整实现及注释

主要组件:Multi-Head Self-Attention (多头自注意力) Position Encoding (位置编码) Feed Forward Network (前馈神经网络) Encoder/Decoder Layer (编码器/解码器层) Complete Transformer Model (完整模型) """import torch import torch.nn as nn import to…...

数据策略与模型算法

数据策略与模型算法数据工程师:更多关心「基建」的问题,比如,数据链路如何构建、如何做技术选型、效率稳定性如何保障等等。 算法工程师:更多关心「模型」的问题,比如,具体某个算法是什么原理,如何调参等等。 数据分析师:运用工具解决「端到端」的问题,包括「问题抽象…...

25fall-cs101 作业图床 - Amy

...

在使用代理的时候,可以使用更简单的C++语法代替FGameplayAttribute代理,使用TStaticFuncPtr T

DECLARE_DELEGATE_REVAL(FGameplayAttribute, FAttributeSignature); 比如这里的代理 定义为FAttributeSignature AttributeSignature ;但是可以不生命代理,直接声明 TBaseStaticDelegateInstance<FGameplayAttribute(), FDefaultDelegateUserPolicy>::FFuncPtr它代表…...

从 url 到 PPT 一键生成:Coze 工作流,颠覆你的内容创作方式!

完整内容:从 url 到 PPT 一键生成:Coze 工作流,颠覆你的内容创作方式!你是否曾在面对大量文章资料,却要在短时间内将其精华提炼并制作成演示文稿时,感到焦头烂额、无从下手?一页页翻阅文章,手动摘取要点,再逐一编排进 PPT,整个过程繁琐又耗时,效率低下不说,最终呈现…...

[WPF学习笔记]多语言切换-001

1、VS2019新建项目2、引入Nuget包 3、修改XML代码引入命名空间并设置<Window x:Class="WPFMultiLanguageTest.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/…...

Shell 语法摘要

sed 的使用 sed 的全称是 Stream Editor,即流编辑器。它可以逐行处理输入数据(先将读入的行放到缓冲区中,对缓冲区里的内容进行处理),并将处理结果输出到标准输出。 格式:sed [选项] [address]{脚本命令(块)} 文件名 前缀 address 可以是数字或者文本(正则),格式:[addr…...

软件设计师知识点总结(一)

一、Linux目录与Windows目录区别 Linux的目录结构是一个树型结构 Windows 系统 可以拥有多个盘符, 如 C盘、D盘、E盘 Linux 没有盘符 这个概念, 只有一个根目录 /, 所有文件都在它下面 二、常见目录介绍(记住重点)目录作用/bin二进制命令所在的目录(普通命令 => 普通用户…...

智能引擎驱动:DRS.Editor让汽车诊断设计效率跃升!

在汽车电子诊断数据管理领域,用户普遍依赖传统的线下 Excel 管理模式,这种离线、文件化的方式常常导致数据分散、版本混乱、共享困难、复用率低,正成为制约开发效率与质量的瓶颈,并带来以下痛点:校验低效易错:诊断数据编写不规范,合法性、逻辑性及完整性校验效率低,易出…...

【译】Visual Studio 2026 Insider 来了!

Visual Studio 2026 Insider 现已发布 —— 这标志着我们在这款 IDE 上迈出了最具雄心的一步。此版本将人工智能直接融入开发者的工作流程,性能方面的改进重新树立了企业级规模下对速度的预期,而现代化的设计则让整个开发环境感觉更轻盈、更专注。并且,我们首次推出了全新的…...

GAS_Aura-Granting Abilities

1...

CH584 CH585 触摸应用介绍一

1、提供的资料工程和功能介绍 | | | |-- TOUCH | | | | |-- TKYLIB:触摸库文件及其头文件 | | | | |-- Touch_EX001:触摸应用的综合演示,包括触摸滑条、触摸滑环、触摸按键和隔空感应四种触摸应用,配合EVB使用。 | | …...

OpenEuler 24.03 (LTS-SP2)安装最新版本docker

OpenEuler 24.03系统默认安装的docker版本是18.09,该版本有重大bug,所以鉴于此安装最新版本docker。 一、配置 Docker 仓库 首先,需要设置 Docker 的官方仓库,和替换为国内的镜像源。 1.安装必要的包:sudo dnf install -y dnf-utils2.设置稳定的仓库: docker官方没有明确…...

西门子SINAMICS S120伺服驱动系统介绍

SINAMICS S120是集V/F、矢量控制及伺服控制于一体的驱动控制系统,可以控制普通的三相异步电动机,还能控制同步电机、扭矩电机及直线电机,属于高性能驱动,是西门子SINAMICS M1级产品。S120产品特点“高度灵活”的模块化设计 允许不同功率等级与控制性能的单元自由组合,所有…...

第10章 STM32 模拟SPI电阻屏触摸配置和测试

前言 硬件的配置由前面的工程递增,会根据目的修改部分控制代码 由于本人较懒,记录主要是过程,原理性的东西网上一大把,我就不赘述了,由于懒,主要由图片和代码加少量文字组成 源码地址https://gitcode.com/qq_36517072/stm32,第x章为cx文件夹一、STM32CUBE配置修改 带的2…...

ABAP同步和异步

在保存增强触发其他单据生成或者自建表保存需要COMMIT WORK 时候使用STARTING NEW TASK 优势是在新会话中提交:在这个新的、独立的上下文中执行 COMMIT WORK,只会提交该 RFC 函数内部自身的数据库操作,而不会影响到主增强程序所在的事务上下文。主程序的数据库更改仍会等待…...

202208_网鼎杯青龙组_CRYPTO

MD5,爆破Tags:MD5,爆破 0x00. 题目 小A鼓起勇气向女神索要电话号码,但女神一定要考考他。女神说她最近刚看了一篇发表于安全顶会USENIX Security 2021的论文,论文发现苹果AirDrop隔空投送功能的漏洞,该漏洞可以向陌生人泄露AirDrop发起者或接收者的电话号码和电子邮箱。小A经…...

Oracle笔记:11GR2 datagruad 环境搭建BORKER

我们的文章会在微信公众号IT民工的龙马人生和博客网站( www.htz.pw )同步更新 ,欢迎关注收藏,也欢迎大家转载,但是请在文章开始地方标注文章出处,谢谢! 由于博客中有大量代码,通过页面浏览效果更佳。Oracle笔记:11GR2 datagruad 环境搭建BORKER 公司所有的DG环境都用到了…...

GAS_Aura-Gameplay Abilities

1简单说明了下GAS运行的情景...

领域驱动设计(DDD)【23】之泛化:从概念到实践

文章目录 一 泛化基础&#xff1a;理解DDD中的核心抽象机制1.1 什么是泛化&#xff1f;1.2 为什么泛化在DDD中重要&#xff1f;1.3 泛化与特化的双向关系 二 DDD中泛化的实现形式2.0 实现形式概览2.1 类继承&#xff1a;最直接的泛化实现2.2 接口实现&#xff1a;更灵活的泛化方…...

零基础langchain实战二:大模型输出格式化成json

零基础langchain实战一&#xff1a;模型、提示词和解析器-CSDN博客 书接上文 大模型输出格式化 在下面例子中&#xff1a;我们需要将大模型的输出格式化成json。 import os from dotenv import load_dotenvload_dotenv() # 加载 .env 文件 api_key os.getenv("DEEPS…...

Python 数据分析:numpy,抽提,整数数组索引

目录 1 代码示例2 欢迎纠错3 免费爬虫------以下关于 Markdown 编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个…...

在项目中如何巧妙使用缓存

缓存 对于经常访问的数据&#xff0c;每次都从数据库&#xff08;硬盘&#xff09;中获取是比较慢&#xff0c;可以利用性能更高的存储来提高系统响应速度&#xff0c;俗称缓存 。合理使用缓存可以显著降低数据库的压力、提高系统性能。 那么&#xff0c;什么样的数据适合缓存…...

C语言字符串

字符串是C语言最核心的概念之一&#xff0c;却也是引发最多Bug的领域。掌握它&#xff0c;你将解锁高效处理文本的能力&#xff1b;忽视细节&#xff0c;则可能陷入内存陷阱。 一、字符串的本质&#xff1a;字符数组 核心规则&#xff1a;C语言用\0&#xff08;ASCII值0&#…...