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

news2024/10/23 15:28:11

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

文章目录

  • 【算法】动态规划:从斐波那契数列到背包问题
    • 1.斐波那契数列
    • 2.爬楼梯
    • 3.零钱转换
      • Python代码
    • 4.零钱兑换 II
    • 5.组合数dp和排列数dp
    • 6.为什么
      • 动态规划的核心思想
      • 计算组合数的正确方法
        • 代码实现
      • 为什么先遍历硬币再遍历金额可以计算组合数
      • 详细解释
        • 举例说明
      • 最终结果
      • 具体组合情况
      • 为什么有效
    • 7.背包问题
      • 01背包问题
        • 定义
      • 完全背包问题
        • 定义
      • 示例
      • 为什么需要倒序遍历
    • 8.结语

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2211498.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++打怪之路Lv8】-- string类

&#x1f308; 个人主页&#xff1a;白子寰 &#x1f525; 分类专栏&#xff1a;重生之我在学Linux&#xff0c;C打怪之路&#xff0c;python从入门到精通&#xff0c;数据结构&#xff0c;C语言&#xff0c;C语言题集&#x1f448; 希望得到您的订阅和支持~ &#x1f4a1; 坚持…

智能汽车智能网联

我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 屏蔽力是信息过载时代一个人的特殊竞争力&#xff0c;任何消耗你的人和事&#xff0c;多看一眼都是你的不…

深入理解线性表--顺序表

目录 顺序表- Seqlist -> sequence 顺序 list 表 顺序表的概念 问题与解答 顺序表的分类 静态顺序表 动态顺序表 问题与解答(递进式) 动态顺序表的实现 尾插 头插 尾删 头删 指定位置插入 指定位置删除 销毁 总结 前言&#xff1a;线性表是具有相同特性的一类数据结构…

【exceljs】纯前端如何实现Excel导出下载和上传解析?

前段时间写过一篇类似的文章&#xff0c;介绍了sheetjs。最近发现了一个更好用的库ExcelJS&#xff0c;它支持高级的样式自定义&#xff0c;并且使用起来也不复杂。实际上sheetjs也支持高级自定义样式&#xff0c;不过需要使用付费版。 下面对比了Exceljs和Sheetjs&#xff1a…

Linux的习题+一道回溯类型的算法题

Linux的习题 Linux环境与版本 1.linux 2.6.* 内核默认支持的文件系统有哪些&#xff1f;[多选] A.ext3 B.ext2 C.ext4 D.xfs E.ufs 正确答案&#xff1a;ABCD A 全称Linux extended file system, extfs,即Linux扩展文件系统&#xff0c;ext2为第二代 D XFS一种高性能的日…

使用频率最高的 opencv 基础绘图操作 - python 实现

以下是 opencv-python 基本操作绘制示例&#xff0c;绘制&#xff1a; 1&#xff09;圆&#xff0c;2&#xff09;矩形&#xff0c;3&#xff09;线段&#xff0c;4&#xff09;文本。 安装 opencv-python pip install opencv-python 在图上绘制圆的操作&#xff0c;示例如…

HCIP-HarmonyOS Application Developer 习题(五)

1、以下哪种原子化布局能力属于自适应变化能力? A. 拉伸 B.占比 C. 隐藏 D.拆行 答案&#xff1a;A 分析&#xff1a;划分为“自适应变化能力”和“自适应布局能力”两类。 其中&#xff0c;自适应变化能力包含了缩放能力和拉伸能力&#xff0c;自适应布局能力包含了隐藏、折…

『Mysql进阶』Mysql explain详解(五)

目录 Explain 介绍 Explain分析示例 explain中的列 1. id 列 2. select_type 列 3. table 列 4. partitions 列 5. type 列 6. possible_keys 列 7. key 列 8. key_len 列 9. ref 列 10. rows 列 11. filtered 列 12. Extra 列 Explain 介绍 EXPLAIN 语句提供有…

Spring学习——SpringAOP

0. IOC思想(DI)1. 关键注解Repositorypublic class DeptDaoImpl1 implements DeptDao{}RepositoryPrimarypublic class DeptDaoImpl2 implements DeptDao{}Servicepublic class DeptServiceImpl implements DeptService{AutowiredQulifier("deptDaoImpl2")private De…

UE5学习笔记24-添加武器弹药

一、给角色的武器添加弹药 1.创建界面&#xff0c;根据笔记23的界面中添加 2.绑定界面控件 UPROPERTY(meta (Bindwidget))UTextBlock* WeaponAmmoAmount;UPROPERTY(meta (Bindwidget))UTextBlock* CarriedAmmoAmount; 3.添加武器类型枚举 3.1创建武器类型枚举头文件 3.2创建文…

C++容器:map

1.铺垫 1.1map和unorder_map&#xff0c;两者的实现思想不同&#xff0c;但是用法是相同的&#xff1b;map的思想是平衡二叉树&#xff1b;unorder_map的思想是哈希&#xff1b; 1.2在现实及做题中&#xff0c;百分之99&#xff0c;都在使用unorder_map&#xff0c;接下来的讲…

2024 第一次周赛

A: 题目大意 骑士每连续 i 天每天会得到 i 个金币&#xff0c;&#xff08;i 1&#xff0c; 2&#xff0c; 3 &#xff0c; …&#xff09;,那么展开看每一天可以得到的金币数&#xff1a;1 2 2 3 3 3 4 4 4 5 5 5 5 5 … 可以发现就是1个1 &#xff0c;2个2, 3个3…,那么我…

P20机型HW睡眠省电-调研

摘要 AI睡眠场景识别 不激进的智能管控 睡眠省电 UI 以前有单独的睡眠开关选项&#xff0c;现在没有了&#xff0c;但是智能充电模式是使用AI睡眠模式 睡眠识别 华为专利上提到的论文《BTP-A Bedtime Predicting Algorithm》 各种事件状态&#xff08;亮灭屏、alarm、主动…

《数字信号处理》学习08-围线积分法(留数法)计算z 逆变换

目录 一&#xff0c;z逆变换相关概念 二&#xff0c;留数定理相关概念 三&#xff0c;习题 一&#xff0c;z逆变换相关概念 接下来开始学习z变换的反变换-z逆变换&#xff08;z反变化&#xff09;。 由象函数 求它的原序列 的过程就称为 逆变换。即 。 求z逆变换…

RTR-Chapter8

第八章 光与颜色 光量辐射度量学光度学色度学使用RGB颜色进行渲染 从场景到屏幕HDR显示编码色调映射色调再现变换曝光 颜色分级 光量 辐射度量学 辐射度量学&#xff08;radiometry&#xff09;研究的是对电磁辐射&#xff08;electromagnetic radiation&#xff09;的测量&am…

linux线程 | 线程的控制(二)

前言&#xff1a; 本节内容是线程的控制部分的第二个小节。 主要是列出我们的线程控制部分的几个细节性问题以及我们的线程分离。这些都是需要大量的代码去进行实验的。所以&#xff0c; 准备好接受新知识的友友们请耐心观看。 现在开始我们的学习吧。 ps:本节内容适合了解线程…

开源情报(OSINT)入门:初学者指南

欢迎来到令人兴奋的开源情报 (OSINT) 世界&#xff01;如果您是该领域的新手&#xff0c;OSINT 就是收集和分析公开信息以获取见解并制作情报产品。 无论您是安全爱好者、记者还是只是对 OSINT 感兴趣&#xff0c;本入门指南都将带您了解基础知识&#xff0c;让您开始第一次&a…

MySQL进阶学习一(2024.10.07版)

2024-10-06 -------------------------------------------------------------------------------------------------------------------------------- 1.一条SQL语句是如何执行的 单进程的多线程模型 MySQL的物理目录 show global variables like "%basedir%"; …

初学Vue(3)(内置指令)

文章目录 十四、内置指令v-textv-htmlv-cloak&#xff08;没有值&#xff09;v-oncev-pre自定义指令一、定义语法二、配置对象中常用的3个问题三、备注 十四、内置指令 回顾&#xff1a; v-bind &#xff1a;单向绑定解析表达式&#xff0c;可简写为 :xxx v-model : 双向数据绑…

HTML快速入门--第一节--五个基本标签

一、网络编程的三大基石 1.1 url 统一资源定位符&#xff1a; 网址:整个互联网中可以唯一且准确的确定一个资源的位置 (url项目外) 网址:https://www.baidu.com/ https://www.baidu.com/ 协议://ip端口/项目名/页面名 协议:交通法规获取资源 ip端口 &#xff08;域名&…