通过例子说明-动态规划

选择>行动>思考,好像是个死循环 -song。

通过例子说明-动态规划_第1张图片

 动态规划(Dynamic Programming,简称DP)是一种解决问题的数学优化方法,通常用于解决具有重叠子问题和最优子结构性质的问题。它的基本思想是将问题拆分成小的子问题,先求解并保存子问题的解,然后通过这些子问题的解来求解原始问题,避免重复计算,从而提高效率。

 最常见的动态规划问题包括最长公共子序列、最短路径、背包问题等。让我们通过一个简单的例子来理解动态规划:

 

例子:斐波那契数列

斐波那契数列是一个经典的动态规划问题。数列的定义是:F(0) = 0,F(1) = 1,F(n) = F(n-1) + F(n-2)(n ≥ 2)。

现在我们想要计算第 n 个斐波那契数,如果直接按照递归定义来计算,会存在大量的重复计算,效率很低。而动态规划的思想是从底向上逐步计算,并保存中间结果。

def fibonacci(n):
    if n <= 1:
        return n

    # 初始化一个数组来保存中间结果
    dp = [0] * (n + 1)
    dp[1] = 1

    # 从底向上逐步计算
    for i in range(2, n + 1):
        dp[i] = dp[i - 1] + dp[i - 2]

    return dp[n]

# 测试
print(fibonacci(5))  # 输出 5

在这个例子中,通过动态规划的方式,我们避免了重复计算,将问题拆解成小的子问题,从而高效地计算出了斐波那契数列的第 n 项。这是动态规划的基本思想,通过保存中间结果,避免重复计算,提高算法效率。

这里可能还有很多朋友不懂,咱们继续通俗一点的说:

当我们解决问题时,有时候问题很大,我们会把它分成一些小问题,解决小问题的过程中,我们会保存一些信息,以便后面解决更大的问题。动态规划就是这样一种策略。

比如,我们想算斐波那契数列的第 n 项,斐波那契数列的规律是:第一个数字是0,第二个数字是1,后面的数字是前两个数字的和。数列的前几项是 0, 1, 1, 2, 3, 5, 8, 13, ... 。

现在,我们要算第 n 项,如果直接算可能会很慢,因为要重复计算很多次相同的数字。动态规划就是为了避免这种重复计算。

我们可以这样做:

  1. 从小问题开始,先算出前两个数字(0和1)。
  2. 把这两个数字存下来。
  3. 然后,计算第三个数字,它是前两个数字的和。存下来。
  4. 依次类推,计算第四个、第五个,一直到第 n 个数字。

在这个过程中,我们不是每次都从头算,而是用之前算过的结果,这样就避免了大量的重复计算。

简而言之,动态规划就是将一个大问题拆成一系列小问题,逐步解决这些小问题,并且把中间结果保存下来,避免重复计算,最终得到整个问题的解。

让我们用一个更简单的例子来解释动态规划:

问题:爬楼梯

假设你要爬一个楼梯,每次你只能迈一步或两步。问:爬到楼梯顶端有多少种不同的方式?

现在,我们可以使用动态规划来解决这个问题。

  1. 定义子问题: 要爬到第 n 级楼梯,我们可以从第 n-1 级走一步,或者从第 n-2 级走两步。

  2. 找出状态转移方程:dp[i] 表示爬到第 i 级楼梯的不同方式数,那么 dp[i] = dp[i-1] + dp[i-2]

  3. 初始化: 对于前两级楼梯,有 dp[0] = 1(不需要爬),dp[1] = 1(一种方式,爬一步)。

  4. 递推: 从第三级楼梯开始,使用状态转移方程逐步计算 dp[2]dp[3],一直到 dp[n]

  5. 返回结果: 最终结果是 dp[n],表示爬到楼梯顶端的不同方式数。

看看这个通俗易懂的 Python 代码:

def climbStairs(n):
    # 初始化
    dp = [0] * (n + 1)
    dp[0] = 1
    dp[1] = 1

    # 递推
    for i in range(2, n + 1):
        dp[i] = dp[i - 1] + dp[i - 2]

    # 返回结果
    return dp[n]

# 测试
result = climbStairs(4)
print(result)  # 输出 5

这里的 dp[4] 就表示爬到第四级楼梯的不同方式数,结果是5。这个例子简单明了,但也展示了动态规划的基本思想:将大问题拆解成小问题,通过保存中间结果避免重复计算,从而高效解决问题。

再讲一个例子:

想象你站在一个楼梯底部,目标是爬到楼梯顶端。你每次可以迈一步或者迈两步。问题是:爬到楼梯顶端,有多少不同的方式?

解释:

  1. 第一步: 如果楼梯只有一级,你只有一种方式,就是迈一步就到了。

  2. 第二步: 如果楼梯有两级,你可以选择迈一步两次或者一次迈两步。所以,有两种方式。

  3. 第三步: 如果楼梯有三级,你可以从第一级迈两步,也可以从第二级迈一步。这样就把问题拆分成了前两级楼梯的问题。

  4. 第四步: 如果楼梯有四级,你可以从第三级迈一步,也可以从第二级迈两步。同样,这又把问题拆分成了前两级楼梯的问题。

这个过程可以一直持续下去。而这就是动态规划的思想:

  • 拆分问题: 将大问题拆分成小问题,先解决小问题的答案。

  • 保存中间结果: 为了避免反复计算,我们保存每一步的答案。

通过这种方式,你可以从楼梯底部一步一步地找到爬到楼梯顶端的不同方式数。

在代码中,我们用一个列表(dp)来保存每一级楼梯的不同方式数。通过迭代计算,最终得到了爬到楼梯顶端的答案。

问题:买糖果

假设你去糖果店,你有一些零钱,而糖果有不同的价格。现在,你想知道用你的零钱有多少种方式可以买到糖果。

  1. 定义小问题: 想象你有一些零钱,要买到一块钱的糖果,有几种方式?要买到两块钱的糖果,有几种方式?

  2. 找出规律: 如果你知道了买到一块钱和两块钱的方式,你就能推导出买到三块钱的方式。因为可以从买到两块钱的情况下再加一个硬币,或者从买到一块钱的情况下再加两个硬币。

  3. 定义状态转移方程:dp[i] 表示用你的零钱买到 i 块钱糖果的不同方式数。那么 dp[i] = dp[i-1] + dp[i-2] + ...

  4. 初始化: 对于买到一块钱的糖果,有 dp[1] = 1 种方式(就是用一块钱买一个)。对于买到两块钱的糖果,有 dp[2] = 2 种方式(可以用两个一块的或一个两块的)。

  5. 递推: 从买到三块钱的情况开始,一直递推到你想知道的金额。

  6. 返回结果: 最终结果是 dp[你的零钱],表示用你的零钱能够购买到糖果的不同方式数。

这个问题和前面爬楼梯的问题类似,只是现在我们在考虑的是零钱和糖果的价钱。

你可能感兴趣的:({easy}算法小题,动态规划,算法,笔记,百度,java)