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

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

文章目录

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数组:

python 复制代码
dp=[0]*(n+1)

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

python 复制代码
dp[1],dp[2]=1,1

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

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

以此类推......

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

python 复制代码
for i in range(3,n+1):
    dp[i]=dp[i-1]+dp[i-2]

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

完整代码:

python 复制代码
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]

是不是很熟悉?

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

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

python 复制代码
dp[1],dp[2]=1,2

所以这道题也就解决了:

python 复制代码
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中,那么代码可以这样写:

python 复制代码
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代码

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](一次可以爬的楼梯阶数)

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

python 复制代码
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]

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

python 复制代码
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

初始化

python 复制代码
amount = 5
coins = [1, 2, 5]
dp = [0] * (amount + 1)
dp[0] = 1

初始状态:

python 复制代码
dp = [1, 0, 0, 0, 0, 0]

外层循环:遍历金额

python 复制代码
for i in range(1, amount + 1):

第一轮:i = 1

python 复制代码
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

python 复制代码
dp = [1, 1, 0, 0, 0, 0]

第二轮:i = 2

python 复制代码
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

python 复制代码
dp = [1, 1, 2, 0, 0, 0]

第三轮:i = 3

python 复制代码
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

python 复制代码
dp = [1, 1, 2, 3, 0, 0]

第四轮:i = 4

python 复制代码
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

python 复制代码
dp = [1, 1, 2, 3, 5, 0]

第五轮:i = 5

python 复制代码
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

python 复制代码
dp = [1, 1, 2, 3, 5, 9]

最终结果

python 复制代码
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的方法数。状态转移方程决定了如何从已知的子问题结果推导出新的问题结果。

计算组合数的正确方法

代码实现
python 复制代码
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. 初始化

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

    python 复制代码
    for coin in coins:

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

  3. 内层循环:遍历金额

    python 复制代码
    for i in range(coin, amount + 1):
        dp[i] += dp[i - coin]

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

详细解释

举例说明

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

  1. 初始化

    python 复制代码
    dp = [1, 0, 0, 0, 0, 0]
  2. 第一轮:coin = 1

    python 复制代码
    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
    python 复制代码
    dp = [1, 1, 1, 1, 1, 1]
  3. 第二轮:coin = 2

    python 复制代码
    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
    python 复制代码
    dp = [1, 1, 2, 2, 3, 3]
  4. 第三轮:coin = 5

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

最终结果

python 复制代码
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] 只考虑当前硬币对当前金额的影响,确保每个组合只被计算一次。

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

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

python 复制代码
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的背包:

物品 重量 价值
A 2 3
B 3 4
C 4 5
D 5 8

我们的目标是选择一些物品放入背包中,使得总价值最大,同时总重量不超过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-某个物品的重量]+该物品的价值)

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

python 复制代码
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数组:

python 复制代码
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背包的代码,并解释了其内部的原理 。这里补充一点,背包问题写代码的顺序应该是先写二维,再压缩成一维,这是背包问题正常的解法及思路。本篇文章提供了理解背包问题的另一个思路,如果大家理解不了背包问题的正常思路,可以参考一下本人的思路,若有一些需要指正的地方,欢迎大家指正!

相关推荐
დ旧言~18 分钟前
【高阶数据结构】图论
算法·深度优先·广度优先·宽度优先·推荐算法
张彦峰ZYF23 分钟前
投资策略规划最优决策分析
分布式·算法·金融
The_Ticker38 分钟前
CFD平台如何接入实时行情源
java·大数据·数据库·人工智能·算法·区块链·软件工程
爪哇学长1 小时前
双指针算法详解:原理、应用场景及代码示例
java·数据结构·算法
Dola_Pan1 小时前
C语言:数组转换指针的时机
c语言·开发语言·算法
繁依Fanyi2 小时前
简易安卓句分器实现
java·服务器·开发语言·算法·eclipse
烦躁的大鼻嘎2 小时前
模拟算法实例讲解:从理论到实践的编程之旅
数据结构·c++·算法·leetcode
C++忠实粉丝2 小时前
计算机网络socket编程(4)_TCP socket API 详解
网络·数据结构·c++·网络协议·tcp/ip·计算机网络·算法
用户37791362947553 小时前
【循环神经网络】只会Python,也能让AI写出周杰伦风格的歌词
人工智能·算法
福大大架构师每日一题3 小时前
文心一言 VS 讯飞星火 VS chatgpt (396)-- 算法导论25.2 1题
算法·文心一言