首页 > 编程语言 >【算法】动态规划:从斐波那契数列到背包问题

【算法】动态规划:从斐波那契数列到背包问题

时间:2024-10-13 21:21:43浏览次数:10  
标签:遍历 数列 硬币 coins 从斐波 amount 背包 那契 dp

【算法】动态规划:从斐波那契数列到背包问题

文章目录

1.斐波那契数列

斐波那契数列(Fibonacci sequence)是一个非常著名的数列,在数学中有着广泛的应用。这个数列的每一项都是前两项的和,通常从1开始。数列的前几项是这样的:

1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, …

数学上,斐波那契数列可以定义为如下递归形式:

F(0) = 0, F(1) = 1, F(n) = F(n-1) + F(n-2),对于 n ≥ 2 的整数。

依据这个递推式,我们将我们想要求的F(n)转换为求F(n-1)F(n-2)的值,这就是动态规划中的状态转移方程。简单来说,F(n)的得来是由F(n-1)F(n-2)决定的。

那么F(n-1)F(n-2)从何而来呢?

我们可以将它先计算好,存起来,用的时候直接拿来用就行。

这就是动态规划中的记忆化:

通过将已经计算过的子问题的结果存储起来,当再次遇到相同的子问题时,可以直接使用存储的结果,从而节省计算时间和资源。

所以如何用动态规划来求出F(n)呢?

动态规划有三大步:

  1. 定义子问题
  2. 定义状态数组dp
  3. 定义状态转移方程

我们有dp[i]来代表F(i)的值

首先我们需要初始化dp数组:

dp=[0]*(n+1)

然后我们先预处理一些值:

dp[1],dp[2]=1,1

这样之后,我们就可以得出dp[3]:

dp[3]=dp[2]+dp[1]

以此类推……

我们用一个for循环就都可以表示出来:

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

最后dp[n]就是我们要求的值了。

完整代码:

def fibonacci(n):
    if n <= 1:return n
    dp = [0] * (n + 1)
    dp[1],dp[2] = 1,1
    for i in range(3, n + 1):
        dp[i] = dp[i - 1] + dp[i - 2]
    return dp[n]

2.爬楼梯

了解这些之后,就可以做一些动态规划的题目了

而力扣的70. 爬楼梯与斐波那契数列非常相似

题目:

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 12 个台阶。你有多少种不同的方法可以爬到楼顶呢?

示例 1:

输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶

示例 2:

输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶

提示:

  • 1 <= n <= 45

该题中,我们只能爬1阶或2阶,求爬到楼顶的方法数

那么首先我们想到该如何爬到楼顶

我们可以在离楼顶差一阶的时候再走1阶,差两阶的时候再走2阶。

由于本题要求爬到楼顶的方法数,所以我们可以定义子问题:求楼梯阶数为i时,我们爬到楼顶所需的方法数。

依据子问题,我们可以定义状态数组:

dp[i]来表示当楼梯阶数为i时,我们爬到楼顶所需的方法数。

而我们又知道:

i是由i-1再走1阶或者i-2再走2阶而来。

所以dp[i](楼梯阶数为i时的方法数)就等于dp[i-1](楼梯阶数为i-1时的方法数)加上dp[i-2](楼梯阶数为i-2时的方法数)

所以我们就可以得出状态转移方程:

dp[i]=dp[i-1]+dp[i-2]

是不是很熟悉?

没错,这和斐波那契数列的状态转移方程一模一样

不过区别在于预处理的值不同:

dp[1],dp[2]=1,2

所以这道题也就解决了:

class Solution(object):
    def climbStairs(self, n):
        """
        :type n: int
        :rtype: int
        """
        if n<=1:return n
        dp=[0]*(n+1)
        dp[1],dp[2]=1,2
        for i in range(3,n+1):
            dp[i]=dp[i-1]+dp[i-2]
        return dp[n]

思考一:该题中的dp[i]=dp[i-1]+dp[i-2]为什么是正确的?

我们将这题升级以下,可爬的阶梯数是不确定的,放在一个数组stairs中,那么代码可以这样写:

class Solution(object):
    def climbStairs(self, n):
        """
        :type n: int
        :type stairs: List[int]
        :rtype: int
        """
        dp=[0]*(n+1)
        dp[0]=1
        for i in range(1,n+1):
            for j in stairs:
                if i-j>=0:dp[i]+=dp[i-j]
        return dp[n]

提示:如果感觉有点难理解请先看下一题

3.零钱转换

接下来我们上升难度,力扣第332题:322. 零钱兑换

题目:

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。

计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1

你可以认为每种硬币的数量是无限的。

示例 1:

输入:coins = [1, 2, 5], amount = 11
输出:3 
解释:11 = 5 + 5 + 1

示例 2:

输入:coins = [2], amount = 3
输出:-1

示例 3:

输入:coins = [1], amount = 0
输出:0

提示:

  • 1 <= coins.length <= 12
  • 1 <= coins[i] <= 231 - 1
  • 0 <= amount <= 104

本题和爬楼梯一题及其相似

我们可以将两题对比着来写:

将该题中的amount类比于爬楼梯中的n(楼梯的阶数);将题中的coins类比于可以爬的阶梯数1、2

只不过这题的coins不是给定的1、2,而是随机的而已。

我们设dp[i]表示当总金额为i时凑成总金额所需的 最少的硬币个数

我们以coins = [1, 2, 5], amount = 11为例,我们知道,硬币就1,2,5这三种,所以当总金额为i时的最少硬币个数的得来就有三种情况:

dp[i-1]时再来拼凑面值1的硬币;

dp[i-2]时再来拼凑面值2的硬币;

dp[i-5]时再来拼凑面值5的硬币

如图所示:

在这里插入图片描述

所以就能得出递推式:

dp[i]=min(dp[i-coin]+1,dp[i])

coin代表的就是硬币的面值

我们的coins[1,2,5]就相当于爬楼梯中我们一次能爬的阶梯的阶数[1,2]

只不过爬楼梯是给定的[1,2],而本题在变化而已,写代码时,只要注意捕获coins的变化就行了

Python代码

class Solution(object):
    def coinChange(self, coins, amount):
        """
        :type coins: List[int]
        :type amount: int
        :rtype: int
        """
        dp=[float('inf')]*(amount+1)  # 初始化dp
        dp[0]=0
        for i in range(1,amount+1):
            for j in coins:           # j代表硬币的面值,由于coins的不确定性,所以得增加一个for来捕获coin(每个硬币的面值)
                if i-j>=0:            # 检验总额是否大于硬币面值
                    dp[i]=min(dp[i-j]+1,dp[i]) # 如果大于,才能就行递推
        return dp[-1] if dp[-1] != float('inf') else -1

思考二:在这一题中你是否看到背包问题的影子?

4.零钱兑换 II

接下来看力扣的第518题:518. 零钱兑换 II

题目:

给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。

请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0

假设每一种面额的硬币有无限个。

题目数据保证结果符合 32 位带符号整数。

示例 1:

输入:amount = 5, coins = [1, 2, 5]
输出:4
解释:有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1

示例 2:

输入:amount = 3, coins = [2]
输出:0
解释:只用面额 2 的硬币不能凑成总金额 3 。

示例 3:

输入:amount = 10, coins = [10] 
输出:1

提示:

  • 1 <= coins.length <= 300
  • 1 <= coins[i] <= 5000
  • coins 中的所有值 互不相同
  • 0 <= amount <= 5000

将这题和爬楼梯作比较,发现几乎一模一样

我们将amount看成n(楼梯的阶数);coins看成[1,2](一次可以爬的楼梯阶数)

所以我们可以很自然的写出以下代码:

def change(self, amount, coins):
    """
    :type amount: int
    :type coins: List[int]
    :rtype: int
    """
    dp=[0]*(amount+1)
    dp[0]=1
    for i in range(1,amount+1):
        for j in coins:
            if i>=j:dp[i]+=dp[i-j]  
    return dp[-1]

我们将这段代码和升级后的爬楼梯的代码作对比:

class Solution(object):
    def climbStairs(self, n):
        """
        :type n: int
        :type stairs: List[int]
        :rtype: int
        """
        dp=[0]*(n+1)
        dp[0]=1
        for i in range(1,n+1):
            for j in stairs:
                if i-j>=0:dp[i]+=dp[i-j]
        return dp[n]

发现:几乎一模一样

不过,我们以amount = 5, coins = [1, 2, 5]为例,输出结果应该是4

但是这个代码的输出结果为9

为什么呢?

我们来模拟一下这段代码的dp

初始化

amount = 5
coins = [1, 2, 5]
dp = [0] * (amount + 1)
dp[0] = 1

初始状态:

dp = [1, 0, 0, 0, 0, 0]

外层循环:遍历金额

for i in range(1, amount + 1):

第一轮:i = 1

for j in coins:
    if i >= j:
        dp[i] += dp[i - j]
  • j = 1:i >= 1(True)
    • dp[1] += dp[1 - 1] => dp[1] += dp[0] => dp[1] += 1 => dp[1] = 1
    • 组合情况:1 = 1

更新后的 dp

dp = [1, 1, 0, 0, 0, 0]

第二轮:i = 2

for j in coins:
    if i >= j:
        dp[i] += dp[i - j]
  • j = 1:i >= 1(True)
    • dp[2] += dp[2 - 1] => dp[2] += dp[1] => dp[2] += 1 => dp[2] = 1
    • 组合情况:2 = 1 + 1
  • j = 2:i >= 2(True)
    • dp[2] += dp[2 - 2] => dp[2] += dp[0] => dp[2] += 1 => dp[2] = 2
    • 组合情况:2 = 2

更新后的 dp

dp = [1, 1, 2, 0, 0, 0]

第三轮:i = 3

for j in coins:
    if i >= j:
        dp[i] += dp[i - j]
  • j = 1:i >= 1(True)
    • dp[3] += dp[3 - 1] => dp[3] += dp[2] => dp[3] += 2 => dp[3] = 2
    • 组合情况:3 = 1 + 1 + 1, 3 = 2 + 1
  • j = 2:i >= 2 (True)
    • dp[3] += dp[3 - 2] => dp[3] += dp[1] => dp[3] += 1 => dp[3] = 3
    • 组合情况:3 = 1 + 2
  • j = 5: i >= 5 (False) -> 跳过

更新后的 dp

dp = [1, 1, 2, 3, 0, 0]

第四轮:i = 4

for j in coins:
    if i >= j:
        dp[i] += dp[i - j]
  • j = 1:i >= 1(True)
    • dp[4] += dp[4 - 1] => dp[4] += dp[3] => dp[4] += 3 => dp[4] = 3
    • 组合情况:4 = 1 + 1 + 1 + 1, 4 = 2 + 1 + 1, 4 = 1 + 2 + 1
  • j = 2: i >= 2(True)
    • dp[4] += dp[4 - 2] => dp[4] += dp[2] => dp[4] += 2 => dp[4] = 5
    • 组合情况:4 = 2 + 2, 4 = 1 + 1 + 2
  • j = 5: i >= 5 (False) -> 跳过

更新后的 dp

dp = [1, 1, 2, 3, 5, 0]

第五轮:i = 5

for j in coins:
    if i >= j:
        dp[i] += dp[i - j]
  • j = 1:``i >= 1`(True)
    • dp[5] += dp[5 - 1] => dp[5] += dp[4] => dp[5] += 5 => dp[5] = 5
    • 组合情况:5 = 1 + 1 + 1 + 1 + 1, 5 = 2 + 1 + 1 + 1, 5 = 1 + 2 + 1 + 1, 5 = 1 + 1 + 2 + 1, 5 = 1 + 1 + 1 + 2
  • j = 2:i >= 2(True)
    • dp[5] += dp[5 - 2] => dp[5] += dp[3] => dp[5] += 3 => dp[5] = 8
    • 组合情况:5 = 2 + 2 + 1, 5 = 2 + 1 + 2, 5 = 1 + 2 + 2
  • j = 5:i >= 5(True)
    • dp[5] += dp[5 - 5] => dp[5] += dp[0] => dp[5] += 1 => dp[5] = 9
    • 组合情况:5 = 5

更新后的 dp

dp = [1, 1, 2, 3, 5, 9]

最终结果

return dp[-1]

返回值为 9

具体组合情况:

  • 5 = 1 + 1 + 1 + 1 + 1
  • 5 = 2 + 1 + 1 + 1
  • 5 = 1 + 2 + 1 + 1
  • 5 = 1 + 1 + 2 + 1
  • 5 = 1 + 1 + 1 + 2
  • 5 = 2 + 2 + 1
  • 5 = 2 + 1 + 2
  • 5 = 1 + 2 + 2
  • 5 = 5

模拟完成后,我们可以很轻松地发现问题所在:出现了很多重复的情况

而爬楼梯这一题中为什么不出错,因为

  • 5 = 1 + 2 + 1 + 1
  • 5 = 1 + 1 + 2 + 1

本身在爬楼梯里面就是两种情况,而在零钱兑换Ⅱ里面确是一种情况。

为什么会这样呢?有什么办法来去掉这些重复的从而解决掉这个问题呢?

首先我们需要理解这两个概念:组合数dp排列数dp

5.组合数dp和排列数dp

  • 组合数DP:当我们关注的是不同元素的组合而不考虑顺序时,应该先遍历物品(例如硬币),再遍历背包容量(例如金额)。这样可以保证每个组合仅被计算一次。
  • 排列数DP:如果我们关心的是排列,即同样的元素以不同的顺序出现被视为不同的结果,那么应该先遍历背包容量,再遍历物品。这样可以确保每个元素在每个容量下都被尝试放置,导致相同的元素组合可能因为放置顺序不同而被多次计算。

通过前面的爬楼梯与零钱兑换Ⅱ两题,想必大家都知道了两者的区别

爬楼梯是排列数dp而零钱兑换是组合数dp

那么我们如何将我们刚刚写的零钱兑换Ⅱ中的排列数dp转换成组合数dp呢?

要想做到这一步,我们应该交换遍历的顺序:先遍历硬币,再遍历金额

为什么呢?

6.为什么

要理解为什么先遍历硬币再遍历金额可以计算组合数,而不是排列数,我们需要从动态规划(DP)的角度来分析这个问题。关键在于DP数组的更新顺序和状态转移方程。

动态规划的核心思想

在动态规划中,我们通常用一个数组dp来存储中间结果。对于零钱兑换问题,dp[i]表示凑足金额i的方法数。状态转移方程决定了如何从已知的子问题结果推导出新的问题结果。

计算组合数的正确方法

代码实现
def change(amount, coins):
    dp = [0] * (amount + 1)
    dp[0] = 1  # 当金额为0时,有一种方法,即不使用任何硬币
    
    # 先遍历硬币
    for coin in coins:
        # 再遍历金额
        for i in range(coin, amount + 1):
            dp[i] += dp[i - coin]
    
    return dp[amount]

为什么先遍历硬币再遍历金额可以计算组合数

  1. 初始化

    dp = [0] * (amount + 1)
    dp[0] = 1  # 当金额为0时,有一种方法,即不使用任何硬币
    
  2. 外层循环:遍历硬币

    for coin in coins:
    

    这一步确保我们在处理每种硬币时,不会重复考虑已经处理过的硬币。这样可以避免产生重复的组合。

  3. 内层循环:遍历金额

    for i in range(coin, amount + 1):
        dp[i] += dp[i - coin]
    

    这一步确保我们在处理每个金额时,只会考虑当前硬币对当前金额的影响。这样可以确保每个组合只被计算一次。

详细解释

举例说明

假设amount = 5coins = [1, 2, 5],我们来逐步模拟这个过程。

  1. 初始化

    dp = [1, 0, 0, 0, 0, 0]
    
  2. 第一轮:coin = 1

    for i in range(1, 6):
        dp[i] += dp[i - 1]
    
    • i = 1: dp[1] += dp[0] => dp[1] += 1 => dp[1] = 1
    • i = 2: dp[2] += dp[1] => dp[2] += 1 => dp[2] = 1
    • i = 3: dp[3] += dp[2] => dp[3] += 1 => dp[3] = 1
    • i = 4: dp[4] += dp[3] => dp[4] += 1 => dp[4] = 1
    • i = 5: dp[5] += dp[4] => dp[5] += 1 => dp[5] = 1
    dp = [1, 1, 1, 1, 1, 1]
    
  3. 第二轮:coin = 2

    for i in range(2, 6):
        dp[i] += dp[i - 2]
    
    • i = 2: dp[2] += dp[0] => dp[2] += 1 => dp[2] = 2
    • i = 3: dp[3] += dp[1] => dp[3] += 1 => dp[3] = 2
    • i = 4: dp[4] += dp[2] => dp[4] += 2 => dp[4] = 3
    • i = 5: dp[5] += dp[3] => dp[5] += 2 => dp[5] = 3
    dp = [1, 1, 2, 2, 3, 3]
    
  4. 第三轮:coin = 5

    for i in range(5, 6):
        dp[i] += dp[i - 5]
    
    • i = 5: dp[5] += dp[0] => dp[5] += 1 => dp[5] = 4
    dp = [1, 1, 2, 2, 3, 4]
    

最终结果

return dp[5]

返回值为 4

具体组合情况

  • 5 = 1 + 1 + 1 + 1 + 1
  • 5 = 1 + 1 + 1 + 2
  • 5 = 1 + 2 + 2
  • 5 = 5

为什么有效

  1. 避免重复组合:通过先遍历硬币,再遍历金额,我们确保每个组合只被计算一次。例如,1 + 2 + 22 + 1 + 2 被视为同一个组合,因为我们在处理硬币1时已经考虑了所有可能的组合方式。
  2. 状态转移方程dp[i] += dp[i - coin] 只考虑当前硬币对当前金额的影响,确保每个组合只被计算一次。

通过这种方式,我们可以有效地计算出组合数,而不是排列数。

所以零钱兑换Ⅱ这一题我们就可以解决了:

class Solution(object):
    def change(self, amount, coins):
        """
        :type amount: int
        :type coins: List[int]
        :rtype: int
        """
        dp=[0]*(amount+1)
        dp[0]=1
        for j in coins:
            for i in range(1,amount+1):
                if i>=j:dp[i]+=dp[i-j]
        return dp[-1]

思考三:你是否在零钱兑换Ⅱ中看到背包问题的影子?

7.背包问题

01背包问题

定义

01背包问题是一种经典的动态规划问题,其中有一组物品,每个物品有一个重量和一个价值。我们需要选择一些物品放入一个容量有限的背包中,使得背包中物品的总价值最大,同时总重量不超过背包的容量。每个物品只能选择0个或1个,不能重复选择。

完全背包问题

定义

完全背包问题与01背包问题类似,但每个物品可以被选择无限次。也就是说,每个物品可以选择0个、1个、2个、…,直到总重量超过背包容量为止。

示例

假设我们有以下物品和一个容量为10的背包:

物品重量价值
A23
B34
C45
D58

我们的目标是选择一些物品放入背包中,使得总价值最大,同时总重量不超过10。

我们先来看完全背包:这是不是和零钱兑换Ⅱ非常相似?

我们可以把物品重量w=[2,3,4,5]看成coins,将容量c看成amount

只不过是问的问题不同而已

一个是求最大价值,一个是求凑成总金额的硬币组合数

这题我们定义dp[i]表示当背包容量为i时所装物品的最大价值,

dp[i]是怎么来的呢?

在这个示例中,当i>=5时,我们有四种选择:

  • dp[i-2]时再装一个价值为3的物品A
  • dp[i-3]时再装一个价值为4的物品B
  • dp[i-4]时再装一个价值为5的物品C
  • dp[i-5]时再装一个价值为8的物品D

所以有:

dp[i]=max(dp[i],dp[i-某个物品的重量]+该物品的价值)

所以参考零钱兑换,我们可以很容易地写出代码:

w=[2,3,4,5]
v=[3,4,5,8]
c=10
dp=[0]*(c+1)
for i in range(1,c+1):
    for j in range(len(w)):
        if i-w[j]>=0:dp[i]=max(dp[i],dp[i-w[j]]+v[j])
print(dp)
# 输出: [0, 0, 3, 4, 6, 8, 9, 11, 12, 14, 16]

这种写法是排列数dp,但这题明显是组合数dp,那为什么写成排列数dp也是正确的呢?

因为完全背包问题本身允许每个物品被选择无限次。这意味着无论我们先遍历物品还是先遍历容量,每个物品在每个容量下的最优解都会被多次考虑,从而不会产生重复计数的问题。

但01背包就不一样了,我们必须老老实实写成组合数dp,因为01背包每个物品只能选择一次。

为了确保每个物品只能选择一次,我们还得倒序遍历dp数组:

w=[2,3,4,5]
v=[3,4,5,8]
c=10
dp=[0]*(c+1)
for j in range(len(w)):
    for i in range(c,0,-1):
        if i-w[j]>=0:dp[i]=max(dp[i],dp[i-w[j]]+v[j])
print(dp)
# 输出: [0, 0, 3, 4, 5, 8, 8, 11, 12, 13, 15]

那么为什么要倒序遍历dp数组呢?

为什么需要倒序遍历

  1. 避免重复使用同一个物品
    • 如果正序遍历数组(从 weight[i]capacity),在更新 dp[j] 时,可能会使用已经更新过的 dp[j - weight[i]],导致同一个物品被多次使用。
    • 例如,假设我们有一个物品A(重量2,价值6),背包容量为4,dp 数组初始化为 [0, 0, 0, 0, 0]
  2. 正序遍历的问题
    • 正序遍历时,dp数组的更新顺序如下:
      • dp[2] = max(dp[2], dp[0] + 6) = 6
      • dp[3] = max(dp[3], dp[1] + 6) = 6
      • dp[4] = max(dp[4], dp[2] + 6) = 12
    • 这里,dp[4] 的值是12,但实际上我们只能选择一次物品A,所以正确的值应该是6。
  3. 倒序遍历的优点
    • 倒序遍历时,dp数组的更新顺序如下:
      • dp[4] = max(dp[4], dp[2] + 6) = 6
      • dp[3] = max(dp[3], dp[1] + 6) = 6
      • dp[2] = max(dp[2], dp[0] + 6) = 6
    • 这里,dp[4] 的值是6,正确地反映了我们只能选择一次物品A。

8.结语

​ 这篇文章主要讲动态规划算法,我们从斐波那契数列讲起,通过力扣中的爬楼梯零钱兑换零钱兑换Ⅱ这几道经典动态规划题,讲了动态规划的基本方法、以及组合数dp排列数dp,并通过它们引出了动态规划中的背包问题。我们结合前面的示例,写出了完全背包01背包的代码,并解释了其内部的原理 。这里补充一点,背包问题写代码的顺序应该是先写二维,再压缩成一维,这是背包问题正常的解法及思路。本篇文章提供了理解背包问题的另一个思路,如果大家理解不了背包问题的正常思路,可以参考一下本人的思路,若有一些需要指正的地方,欢迎大家指正!

标签:遍历,数列,硬币,coins,从斐波,amount,背包,那契,dp
From: https://blog.csdn.net/Janium/article/details/142901752

相关文章

  • [题目记录]一本通高手训练-数列
    题意定义n-数列为满足以下条件的数列\({a_i}\):数列长度不小于\(3\),且每个元素为\(1\)到\(n\)的整数.对于任意\(k\ge3\),有$(a_k-a_{k-2})(a_{k-1}-a_{k-2})<0$.给出\(n\),求n-数列的个数,对\(10^9+7\)取模.\(n\le10^{500000}\)时空限制......
  • LOJ 数列分块入门2
    算法考虑求小于给定值的数的个数,可以给每个块再维护一个已经排好序的数组,整块加法对于这个块排好序的数组没有影响,零散块加法直接在原序列上加,再将零散块所处的整块从新排序。查询时零散块暴力查找,整块二分查找代码#include<bits/stdc++.h>#defineintlonglongconstintM......
  • 洛谷P2513 逆序对数列
    Problem给定n,k,求得1~n中有多少种排列使得逆序对个数为k?(n,k<=1000)Solve考虑DP:设f[i][j]为i个数中逆序对数量为j的排列数量但因为我们并不知道我们这i个数到底是谁,难以通过以前的状态得到设f[i][j]为将i放入之前的排列后,逆序对数量为k的排列数此时我们发现可以确定前i-1......
  • 代码随想录算法训练营 | 动态规划,509. 斐波那契数,70. 爬楼梯, 746. 使用最小花费爬楼梯
    动态规划:1.动态规划中每一个状态一定是由上一个状态推导出来的2.确定dp数组(dptable)以及下标的含义,确定递推公式dp,数组如何初始化,确定遍历顺序,举例推导dp数组;3.Debug:dp数组打印509.斐波那契数题目链接:509.斐波那契数文档讲解︰代码随想录(programmercarl.com)视频讲解︰斐波那......
  • Day 28 动态规划part01| LeetCode 509.斐波那契数,70.爬楼梯,746.使用最小花费爬楼梯
    理论基础包含题目类别:基础类(斐波那契、爬楼梯)、背包问题、打家劫舍、股票问题、子序列问题解题关键DP数组定义以及下标的含义递推公式DP数组如何初始化遍历顺序打印DP数组509.斐波那契数509.斐波那契数classSolution{publicintfib(intn){......
  • 一本通 1071:菲波那契数
    【题目描述】菲波那契数列是指这样的数列:数列的第一个和第二个数都为1,接下来每个数都等于前面2个数之和。给出一个正整数k,要求菲波那契数列中第k个数是多少。【输入】输入一行,包含一个正整数k。(1≤k≤46)【输出】输出一行,包含一个正整数,表示菲波那契数列中第k个数的大小......
  • 数列 题解
    题意给出一张联通图,图上每个点有红蓝两色,给每一条边都赋一个权值,使得所有的红边构成一颗最小生成树,且权值是\(1\)到\(m\)的排列,求字典序最小的情况。题解对于一条蓝边,其权值一定小于除它以外全是红边的环上的所有红边的权值(有点绕),否则就构不成一颗生成树。所以只需要按顺......
  • 信息学奥赛复赛复习11-CSP-J2020-04方格取数-动态规划、斐波那契数列、最优子结构、重
    PDF文档公众号回复关键字:202410041P7074[CSP-J2020]方格取数[题目描述]设有n×m的方格图,每个方格中都有一个整数。现有一只小熊,想从图的左上角走到右下角,每一步只能向上、向下或向右走一格,并且不能重复经过已经走过的方格,也不能走出边界。小熊会取走所有经过的方格中......
  • 【递归】小q的数列
    https://ac.nowcoder.com/acm/contest/21763/1002pow(2,ans)计算的是2的ans次幂,但是pow()函数返回的是double类型的结果。由于pow()函数主要用于浮点数计算,它返回浮点数结果,而后你可能需要对该结果进行整数操作。如果不进行显式类型转换,这个浮点数结果会丢失精度,特别是在......
  • P8808 [蓝桥杯 2022 国 C] 斐波那契数组
    Hello大家好,我是小亦,今天一大早就来更东西了嘿嘿,不知道现在大家有没有回老家的或去玩的,那有没有扎在家的,呜呜呜我就是宅在家的QWQ,唉没办法啊,好那么好不了这些了qwq,今天我来讲的题目是来自蓝桥杯2022年国C题目也就是第三道题,名叫:斐波那契数组,其实这道题呢,嗯比较的难所以呢我也......