基础知识-动态规划

动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法。

20世纪50年代初美国数学家R.E.Bellman等人在研究多阶段决策过程(multistep decision process)的优化问题时,提出了著名的最优化原理(principle of optimality),把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解,创立了解决这类过程优化问题的新方法——动态规划。

1957年出版了他的名著《Dynamic Programming》,这是该领域的第一本著作。

动态规划只能应用于有最优子结构的问题。最优子结构的意思是局部最优解能决定全局最优解(对有些问题这个要求并不能完全满足,故有时需要引入一定的近似)。简单地说,问题能够分解成子问题来解决。

动态规划(Dynamic Programming,简称DP)是一种算法设计技术,它通常用于求解具有重叠子问题和最优子结构特性的复杂问题。动态规划算法能够将一个复杂问题分解成一系列简单的子问题,并通过合理的方式保存子问题的解,以避免重复计算,从而提高效率。

动态规划的关键特点包括:

  1. 重叠子问题:在解决原问题的过程中,相同的子问题会被多次遇到和求解。
  2. 最优子结构:原问题的最优解包含了其子问题的最优解。也就是说,我们可以通过组合子问题的最优解来构造原问题的最优解。
  3. 存储子问题的解:动态规划算法通常会使用一个表(通常是数组或哈希表)来存储子问题的解,这样在下一次遇到相同的子问题时,可以直接查表获得答案,而不是重新计算。
  4. 构建解决方案:动态规划通常从最简单的子问题开始求解,并逐步构建更复杂问题的解,直到得到原问题的解。

动态规划算法通常有两种实现方式:

  • 自顶向下:这种方法使用递归来解决问题,并通过“记忆化”(Memoization)技术来存储已经计算过的子问题解,以避免重复计算。
  • 自底向上:这种方法通常使用迭代来解决问题,从最小的子问题开始,逐步构建所有相关子问题的解,直到得到原问题的解。

动态规划算法被广泛应用于各种领域,包括但不限于:

  • 最优路径问题:比如在图中找到从一个节点到另一个节点的最短路径(如Dijkstra算法)。
  • 资源分配问题:如背包问题,决定如何装载物品以达到最大价值。
  • 字符串操作问题:如编辑距离问题,求出将一个字符串转换为另一个字符串的最少操作数。
  • 序列问题:如最长公共子序列(LCS)问题,寻找两个序列的最长公共子序列。

动态规划算法的效率通常比简单的递归或穷举方法要高得多,但它也需要更多的思考和精心设计,以确保所有子问题都被正确解决并存储。

解题模板

动态规划(Dynamic Programming,DP)是一种将复杂问题分解为更小子问题来解决的算法思想,它通常用于求解最优化问题。动态规划问题的关键点在于找出状态转移方程,这个方程描述了问题的不同阶段(状态)之间的关系。以下是动态规划算法的一般步骤或解题模板:

  1. 定义状态:确定状态表示的含义,通常状态是用来描述问题解的某个性质,如最长、最短、最大、最小等。状态通常是一个或多个变量的组合。
  2. 确定状态转移方程:状态转移方程是动态规划的核心,它描述了如何从一个或多个较小的子问题的解来得到当前问题的解。通常需要分析问题的特点和规律,找出状态之间的关系。
  3. 确定初始状态和边界条件:初始状态通常是问题的起始点,边界条件则是问题解的特殊情况,要确保这些特殊情况能够正确处理。
  4. 确定计算顺序:根据问题的特点,确定计算状态的顺序,确保在计算一个状态时所依赖的状态已经被计算过。
  5. 实施状态计算:按照确定的顺序,逐个计算状态,直至得到最终的解。
  6. 构建解(可选):有些问题可能需要根据状态值重构解的过程,而不仅仅是求出最优值。

以下是一个简化的动态规划模板,以一个一维DP数组为例:

# 初始化DP数组,长度为问题规模+1,初始化为0或负无穷(取决于问题类型)
dp = [0] * (n + 1)

# 设置初始状态
dp[0] = base_value

# 计算所有状态
for i in range(1, n + 1):
    # 根据状态转移方程更新dp[i]
    dp[i] = ...

# 答案是最后一个状态或状态数组中的最优值
answer = dp[n]

在实际问题中,动态规划的状态可能是多维的,状态转移方程可能更为复杂,计算顺序可能是多层嵌套的循环,但基本的思想和步骤是一致的。解决动态规划问题的关键在于理解问题的本质,找到正确的状态表示和状态转移方程。

解题模板

def dynamic_programming(problem):
    # Step 1: 定义状态
    # dp[i] 表示达到状态 i 时的最优解值
    # 根据问题的不同,状态的定义和维度可能会有所变化

    # Step 2: 初始化状态
    # 根据问题的实际情况初始化状态,通常 dp[0] 或 dp[0][0] 是已知的
    dp = initialize_dp(problem)

    # Step 3: 状态转移
    # 根据状态转移方程计算每个状态的值
    for i in range(1, len(dp)):
        dp[i] = state_transition(dp, i, problem)

    # Step 4: 构造最终解
    # 根据计算出的状态值构造问题的最终解
    solution = construct_solution(dp, problem)
    return solution

# 辅助函数:初始化状态
def initialize_dp(problem):
    # 实现状态初始化的逻辑
    pass

# 辅助函数:状态转移方程
def state_transition(dp, i, problem):
    # 实现状态转移的逻辑
    # 通常涉及到对之前状态的引用和计算
    pass

# 辅助函数:构造最终解
def construct_solution(dp, problem):
    # 实现最终解的构造逻辑
    pass

应用场景

在 LeetCode 上,动态规划 (DP) 是解决很多问题的关键技术之一。动态规划适用于解决具有重叠子问题和最优子结构特性的问题。以下是一些常见的问题类别,其中经常使用动态规划方法:

  1. 序列问题:涉及到数组或字符串,需要找到最长子序列、最大子数组和等。
  2. 最长递增子序列 (Longest Increasing Subsequence)
  3. 最大子数组和 (Maximum Subarray)
  4. 最长公共子序列 (Longest Common Subsequence)
  5. 背包问题:在有限的资源条件下,选择最优的物品组合。
  6. 0/1背包问题
  7. 完全背包问题
  8. 多重背包问题
  9. 编辑距离问题:涉及字符串之间的转换和操作。
  10. 编辑距离 (Edit Distance)
  11. 删除操作 (Delete Operation for Two Strings)
  12. 分割问题:将一个数据结构(如数组、字符串)分割成多个部分,以满足某种最优条件。
  13. 分割数组的最大值 (Split Array Largest Sum)
  14. 分割回文串 (Palindrome Partitioning)
  15. 股票买卖问题:涉及时序数据,需要在最佳时机买卖股票。
  16. 买卖股票的最佳时机 (Best Time to Buy and Sell Stock)
  17. 路径问题:在网格或图中找到最优路径。
  18. 不同路径 (Unique Paths)
  19. 最小路径和 (Minimum Path Sum)
  20. 动态规划与数学:涉及数学计算和规划。
  21. 整数拆分 (Integer Break)
  22. 不同的二叉搜索树 (Unique Binary Search Trees)
  23. 动态规划与树结构:涉及树结构,需要找到最优子结构。
  24. 二叉树的最大路径和 (Binary Tree Maximum Path Sum)
  25. 打家劫舍 III (House Robber III)
  26. 动态规划与位操作:涉及位操作的优化问题。
  27. 比特位计数 (Counting Bits)

这些只是动态规划在 LeetCode 上的一些应用示例。实际上,任何可以通过递归解决且具有重叠子问题的最优化问题都可能适合使用动态规划来解决。解决这些问题时,通常需要识别问题的状态以及如何从子问题的解构建出原问题的解,这就是所谓的状态转移方程。掌握动态规划的关键在于理解这些问题的共同模式,并能够灵活地将这些模式应用到新问题中。

```python
class BertPooler(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.dense = nn.Linear(config.hidden_size, config.hidden_size)
        self.activation = nn.Tanh()

    def forward(self, hidden_states):
        # We "pool" the model by simply taking the hidden state corresponding
        # to the first token.
        first_token_tensor = hidden_states[:, 0]
        pooled_output = self.dense(first_token_tensor)
        pooled_output = self.activation(pooled_output)
        return pooled_output
from transformers.models.bert.configuration_bert import *
import torch
config = BertConfig.from_pretrained("bert-base-uncased")
bert_pooler = BertPooler(config=config)
print("input to bert pooler size: {}".format(config.hidden_size))
batch_size = 1
seq_len = 2
hidden_size = 768
x = torch.rand(batch_size, seq_len, hidden_size)
y = bert_pooler(x)
print(y.size())
```

  • 13
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值