蓝桥杯第十五届抱佛脚(九)动态规划

news2024/11/24 10:50:20

蓝桥杯第十五届抱佛脚(九)动态规划

基本概念

动态规划(Dynamic Programming, DP)是一种用于解决复杂问题的优化算法设计技术。它将原问题分解为若干相互重叠的子问题,通过记录子问题的解,避免重复计算,从而大大减少了计算量。

动态规划典型的应用场景包括:

  1. 最优化问题:如求最短路径、最小编辑距离等。

  2. 计数问题:如有多少种方式走到终点、排列组合数量等。

  3. 取值问题:如背包问题、切钢条问题等。

动态规划的关键特征

最优子结构

  • 问题的最优解包含其子问题的最优解。这意味着问题可以通过组合子问题的解来解决。

  • 动态规划问题的最优子结构性质是指原问题的最优解可以由其子问题的最优解推导出来。也就是说,原问题的解是以子问题的最优解为基础,经过一定的组合或运算得到的。

  • 这个性质反映了原问题与子问题之间的关系:子问题的最优解构成了原问题最优解的一部分。因此,我们可以先求解子问题,然后根据子问题的最优解推导出原问题的最优解。

  • 通常我们使用状态转移方程来刻画这种子问题与原问题之间的关系。状态转移方程定义了如何由小规模子问题的最优解,组合得到大规模问题的最优解。

  • 所以最优子结构性质是动态规划的基础,它保证了我们能够通过解决子问题,逐步推导出原问题的最优解,从而达到将大问题分解为小问题解决的目的。满足这个性质是采用动态规划算法的前提条件。

重叠子问题

  • 在求解过程中,相同的子问题会多次出现。动态规划通过记忆化(存储子问题的解)来避免重复计算。
  • 重复子问题性质指的是在求解一个动态规划问题时,每个子问题都会被重复计算多次。换句话说,不同的较大规模的原问题,存在着相同的较小规模的子问题。
  • 比如在计算斐波那契数列时,f(n)需要先计算f(n-1)和f(n-2),而计算f(n-1)又需要计算f(n-2),这里f(n-2)就是一个重复计算的子问题。
  • 重复子问题的存在,使得相同的子问题被重复计算多次,造成了大量的计算冗余。而动态规划的思想就是:每个子问题只解决一次,将其结果保存在一个表格中,下次需要相同的子问题时,可以直接查表获得结果。
  • 满足重复子问题性质意味着我们可以用动态规划来有效地解决问题,减少重复计算。而如果一个问题在求解时没有出现重复子问题,那我们可以直接使用recursion递归或者更高效的方法来解决,不需要动态规划。
  • 所以重复子问题性质是动态规划相比其他方法优越性的体现,但不是使用动态规划的必要条件。只要满足最优子结构性质,就可以用动态规划求解。

动态规划的基本步骤

动态规划的基本步骤是一套系统的方法论,用于将复杂问题分解为更小、更易于管理的子问题。这些步骤有助于高效解决具有重叠子问题和最优子结构特征的问题。以下是动态规划的五个基本步骤:

1. 识别问题类型

确认问题是否适合用动态规划解决。关键是检查问题是否具有以下两个特性:

  • 最优子结构:问题的最优解包含其子问题的最优解。
  • 重叠子问题:问题可以分解为重复出现的子问题。

2. 定义状态

  • 状态的选择:准确定义出解决问题所需的状态。这通常涉及到找出一个或多个变量来描述一个问题的方方面面。
  • 状态表示:通常使用数组或矩阵来表示状态,例如dp[i]dp[i][j]。其中dp是一个常用的命名约定,代表“动态规划”。

3. 确定状态转移方程

  • 状态转移方程是动态规划的核心,它描述了如何从一个或多个已知状态得到另一个状态。对于每个状态,你需要考虑所有可能的转移,并选择能够达到问题要求的最优或最小化/最大化结果的转移。
  • 这一步需要对问题进行深入分析,确保方程正确地反映了问题的所有方面。

4. 确定边界条件

  • 定义初始状态或基本情况,这是计算过程的起点。例如,在递归实现中,这通常是递归的基准情况。
  • 这些条件必须足够简单,以便可以直接求解,从而为更复杂的状态提供起始点。

5. 计算最终结果

  • 根据上述步骤定义的状态和转移方程,你可以计算出问题的解。
  • 解的计算可以是自顶向下(从大问题到小问题,通常使用递归和记忆化),也可以是自底向上(从基础情况开始逐步构建,通常使用迭代)。

6. (可选)路径重构

  • 在某些情况下,仅仅知道最优解的值是不够的,你可能还需要知道如何达到这个最优解。这可能涉及到回溯状态转移过程,来找出导致最终解的选择序列或路径。

动态规划的分类

自顶向下的动态规划(Top-Down)

  • 这种方法使用递归来解决问题,从最大的问题开始并逐步分解为更小的子问题。
  • 通常结合“记忆化”(Memorization)使用,即存储已解决的子问题的结果,以避免重复计算。
  • 更符合问题的自然形态,但可能会因过多的递归调用而导致性能问题。

自底向上的动态规划(Bottom-Up)

  • 从最小的子问题开始,逐步合成更大的问题的解。
  • 通常使用迭代方法,通过填充表格(一般是数组或矩阵)的方式来记录子问题的解。
  • 通常更高效,因为它避免了递归的开销,并且可以更容易地进行状态转移。

动态规划例题

自顶向下的动态规划(Top-Down)

斐波那契数列
问题描述

斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 01 开始,后面的每一项数字都是前面两项数字的和。也就是:

F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1

给定 n ,请计算 F(n)

常规递归解题
  • 重复计算:在计算如斐波那契数列这样的问题时,递归方法可能会多次计算相同的子问题。例如,在计算 F(n) 的过程中,F(n-2) 会在计算 F(n-1)F(n) 时被重复计算。

  • 效率问题:由于重复计算,递归方法的时间复杂度可能会非常高。对于斐波那契数列的递归实现,时间复杂度是指数级的(大约是 O(2^n)),这对于较大的 n 是非常低效的。

class Solution {
    public int fib(int n) {
        if (n <= 1) {
            return n;
        }
        return fib(n-2) + fib(n-1);
    }
}
动态规划解题
  1. 定义状态

    • 创建一个数组 dp,其中 dp[i] 表示斐波那契数列的第 ( i ) 项。
  2. 初始状态

    • 因为斐波那契数列是由 0 和 1 开始的,所以 dp[0] = 0dp[1] = 1
  3. 状态转移方程

    • 根据斐波那契数列的定义,第 ( n ) 项是其前两项的和,即 dp[i] = dp[i - 1] + dp[i - 2]
  4. 计算顺序

    • 从第 2 项开始,直到第 ( n ) 项。
  5. 答案

    • dp[n] 就是问题的答案。
class Solution {
    public int fib(int n) {
        // Step 1 & 2: 初始化dp数组和初始状态
        if (n <= 1) {
            return n;
        }
        int[] dp = new int[n + 1];
        dp[0] = 0;
        dp[1] = 1;

        // Step 3 & 4: 使用状态转移方程计算dp数组中的每一项
        for (int i = 2; i <= n; i++) {
            dp[i] = dp[i - 1] + dp[i - 2];
        }

        // Step 5: 返回答案
        return dp[n];
    }
}

在这个实现中,我们使用了一个大小为 n + 1 的数组 dp 来存储斐波那契数列的每个值,避免了重复计算相同项的问题,这是动态规划的核心优势。对于大值的 n,这种方法比递归方法要高效得多。

最长递增子序列
问题描述

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例:

输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
动态规划的步骤
  1. 定义状态

    • 创建一个数组 dp,其中 dp[i] 表示以 nums[i] 结尾的最长递增子序列(LIS)的长度。
  2. 初始状态

    • 初始化 dp 的每个元素为 1,因为每个元素自身可以看作是长度为 1 的递增子序列。
  3. 状态转移方程

    • 对于每个 i(从 1 到 nums.length - 1),对于每个 j(从 0 到 i - 1),如果 nums[i] > nums[j],则 dp[i] = Math.max(dp[i], dp[j] + 1)
  4. 计算顺序

    • 依次计算 dp[1], dp[2], ..., dp[nums.length - 1]
  5. 答案

    • 最长递增子序列的长度为 dp 数组中的最大值。
class Solution {
    public int lengthOfLIS(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        int n = nums.length;
        int[] dp = new int[n];
        int maxLength = 1;

        // Step 2: 初始化dp数组
        for (int i = 0; i < n; i++) {
            dp[i] = 1;
        }

        // Step 3 & 4: 动态规划计算
        for (int i = 1; i < n; i++) {
            for (int j = 0; j < i; j++) {
                if (nums[i] > nums[j]) {
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
            }
            // 更新最长长度
            maxLength = Math.max(maxLength, dp[i]);
        }

        // Step 5: 返回答案
        return maxLength;
    }
}

我们首先初始化一个 dp 数组,用来存储以每个元素结尾的最长递增子序列的长度。通过双层循环,我们不断更新 dp 数组中的值,从而找到最长递增子序列。最后,从 dp 数组中找到最大值,这就是我们要求的最长递增子序列的长度。

硬币兑换
问题描述

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

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

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

示例:

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

这个问题可以通过动态规划来解决。我们将问题分解为较小的子问题,然后找出每个子问题的解,从而找到最终问题的解。以下是使用动态规划求解最少硬币个数问题的详细步骤:

动态规划的步骤
  1. 定义状态

    • 创建一个数组 dp,其中 dp[i] 表示组成金额 i 所需的最少硬币个数。
  2. 初始状态

    • 初始化 dp[0] = 0,因为组成金额 0 不需要任何硬币。
    • 对于所有其他 i(1 到 amount),初始化为一个大数(例如 amount + 1),表示初始时无法达到该金额。
  3. 状态转移方程

    • 对于每个金额 i,遍历所有硬币面额。如果 i 大于等于当前硬币面额 coin,则 dp[i] = Math.min(dp[i], dp[i - coin] + 1)

    状态转移方程的核心详解:

    1. 遍历每个金额 i

      • 我们考虑从 1amount 的每个金额。对于每个这样的金额 i,我们要找出组成这个金额所需的最少硬币个数。
    2. 遍历所有硬币面额

      • 对于金额 i,我们检查每种硬币面额 coin。这包括数组 coins 中的每个元素。
    3. 检查条件 i >= coin

      • 我们只有在金额 i 大于等于硬币面额 coin 时才考虑这枚硬币。如果 i < coin,这枚硬币太大,不能用来组成金额 i
    4. 更新 dp[i]

      • 现在,如果金额 i 大于等于硬币面额 coin,我们尝试用这枚硬币来组成金额 i。如果用这枚硬币,那么我们还需要组成金额 i - coin。为此,我们查看 dp[i - coin],它表示组成金额 i - coin 所需的最少硬币个数。
      • 然后,我们使用 dp[i - coin] + 1 来更新 dp[i]。这里加 1 是因为我们使用了一枚面额为 coin 的硬币。这实际上是说:“如果我使用这枚硬币,那么组成金额 i 所需的总硬币数是组成 i - coin 所需的硬币数加上这一枚硬币”。
    5. 取最小值

      • 我们用 Math.min(dp[i], dp[i - coin] + 1) 来更新 dp[i]。这意味着我们对于每种硬币,都检查是否使用它会得到更少的硬币总数。我们选择能组成金额 i 的最少硬币数量。

    通过这种方式,dp[i] 最终存储的是组成金额 i 所需的最少硬币个数。这个过程会对所有金额和所有硬币组合进行尝试,以找出最优解。

  4. 计算顺序

    • 从小到大计算 dp[1], dp[2], ..., dp[amount]
  5. 答案

    • 如果 dp[amount] 大于 amount,意味着无法组成该金额,返回 -1;否则返回 dp[amount]
class Solution {
    public int coinChange(int[] coins, int amount) {
        int max = amount + 1;
        int[] dp = new int[amount + 1];
        Arrays.fill(dp, max);
        dp[0] = 0;

        for (int i = 1; i <= amount; i++) {
            for (int coin : coins) {
                if (i >= coin) {
                    dp[i] = Math.min(dp[i], dp[i - coin] + 1);
                }
            }
        }

        return dp[amount] > amount ? -1 : dp[amount];
    }
}

dp 数组存储了组成每个金额所需的最少硬币个数。通过遍历所有的硬币面额和所有可能的金额,我们能够填充 dp 数组,找到组成特定金额所需的最少硬币个数。如果最终的 dp[amount] 值仍然大于 amount,这意味着没有合适的硬币组合可以组成这个金额,因此返回 -1

编辑距离
问题描述

给你两个单词 word1word2请返回将 word1 转换成 word2 所使用的最少操作数

你可以对一个单词进行如下三种操作:

  • 插入一个字符
  • 删除一个字符
  • 替换一个字符
动态规划的步骤
  1. 定义状态

    • 创建一个二维数组 dp,其中 dp[i][j] 表示 word1 的前 i 个字符转换成 word2 的前 j 个字符所需的最少操作数。
  2. 初始状态

    • 初始化 dp[0][j] = j,表示将空字符串转换为 word2 的前 j 个字符需要 j 步(全部插入操作)。
    • 初始化 dp[i][0] = i,表示将 word1 的前 i 个字符转换为空字符串需要 i 步(全部删除操作)。
  3. 状态转移方程

    • 对于每个 i > 0j > 0,我们考虑以下情况:
      • 如果 word1[i - 1] == word2[j - 1],则无需操作,dp[i][j] = dp[i - 1][j - 1]
      • 否则,我们可以进行插入、删除或替换操作,取这三种操作中最小的一个:
        • 插入:dp[i][j - 1] + 1
        • 删除:dp[i - 1][j] + 1
        • 替换:dp[i - 1][j - 1] + 1

    状态转移方程解析

    状态转移方程考虑的是将 word1 的前 i 个字符转换成 word2 的前 j 个字符所需的最少操作数。设 dp[i][j] 为这个最少操作数。转换方法有三种:插入、删除、替换。我们需要找到最优的操作序列,使得操作次数最少。状态转移方程如下:

    • word1[i - 1] == word2[j - 1]

      • 当前字符相等,不需要任何操作。因此,我们只需考虑 word1 的前 i-1 个字符和 word2 的前 j-1 个字符。所以,dp[i][j] = dp[i - 1][j - 1]
    • word1[i - 1] != word2[j - 1]

      • 当前字符不相等,我们有三种操作方式,选择其中操作数最少的一种:
        1. 插入:在 word1i 位置插入一个字符,使其与 word2[j] 相等。然后,我们需要将 word1 的前 i 个字符变成 word2 的前 j-1 个字符。因此,操作数为 dp[i][j-1] + 1
        2. 删除:删除 word1[i],然后将 word1 的前 i-1 个字符变成 word2 的前 j 个字符。操作数是 dp[i-1][j] + 1
        3. 替换:将 word1[i] 替换成 word2[j],接下来只需将 word1 的前 i-1 个字符变成 word2 的前 j-1 个字符。操作数为 dp[i-1][j-1] + 1

    在每一步,我们选择这三种操作中最小的操作数作为 dp[i][j] 的值。

    简单举例

    例如,考虑 word1 = "abc"word2 = "yabd"。如果我们要计算 dp[3][4](即将 "abc" 变成 "yabd" 的最少操作数):

    1. word1[3-1] != word2[4-1]c != d),所以我们考虑三种操作:
      • 插入:将 d 插入到 word1 的末尾,然后我们只需要考虑将 "abc" 变为 "yab",即 dp[3][3] + 1
      • 删除:删除 word1 的最后一个字符 c,然后我们只需将 "ab" 变为 "yabd",即 dp[2][4] + 1
      • 替换:将 word1 的最后一个字符 c 替换为 d,然后我们只需将 "ab" 变为 "yab",即 dp[2][3] + 1

    我们从这三个操作中选择最小的一个来更新 dp[3][4]

    通过这种方式,我们可以构建整个 dp 数组,最终 dp[word1.length()][word2.length()] 就是将 word1 转换成 word2 所需的最少操作数。

  4. 计算顺序

    • 按行和列依次计算 dp[i][j]
  5. 答案

    • dp[word1.length()][word2.length()] 是最终答案。
class Solution {
    public int minDistance(String word1, String word2) {
        int m = word1.length();
        int n = word2.length();
        int[][] dp = new int[m + 1][n + 1];

        // Step 2: 初始化dp数组
        for (int i = 0; i <= m; i++) {
            dp[i][0] = i;
        }
        for (int j = 0; j <= n; j++) {
            dp[0][j] = j;
        }

        // Step 3 & 4: 计算dp数组
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
                    dp[i][j] = dp[i - 1][j - 1];
                } else {
                    dp[i][j] = Math.min(dp[i - 1][j - 1], Math.min(dp[i][j - 1], dp[i - 1][j])) + 1;
                }
            }
        }

        // Step 5: 返回答案
        return dp[m][n];
    }
}

自底向上的动态规划(Bottom-Up)

爬楼梯
问题描述

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

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

动态规划的步骤
  1. 定义状态

    • 创建一个数组 dp,其中 dp[i] 表示到达第 i 阶楼梯有多少种不同的方法。
  2. 初始状态

    • dp[0] = 1dp[1] = 1。到达第0阶(起点)只有1种方法(即不爬),到达第1阶也只有1种方法(爬1阶)。
  3. 状态转移方程

    • 对于 i >= 2,每一阶楼梯都可以从前一阶爬上来(一步),或者从前两阶跨两步爬上来。因此,dp[i] = dp[i - 1] + dp[i - 2]
  4. 计算顺序

    • dp[2] 开始计算,一直到 dp[n]
  5. 答案

    • dp[n] 就是到达第 n 阶楼梯的不同方法数量。
class Solution {
    public int climbStairs(int n) {
        if (n <= 1) {
            return 1;
        }
        int[] dp = new int[n + 1];
        dp[0] = 1;
        dp[1] = 1;

        for (int i = 2; i <= n; i++) {
            dp[i] = dp[i - 1] + dp[i - 2];
        }

        return dp[n];
    }
}

我们首先处理了两个初始状态 dp[0]dp[1],然后利用状态转移方程计算出每一阶楼梯的爬法数。最后,dp[n] 就给出了爬到第 n 阶楼梯的不同方法数量。这个问题本质上和斐波那契数列是相同的,因为每一阶的方法数都是前两阶方法数的和。

最大子数组和
问题描述

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例:

输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
动态规划的步骤
  1. 定义状态

    • 创建一个数组 dp,其中 dp[i] 表示以 nums[i] 结尾的最大子数组和。
  2. 初始状态

    • dp[0] = nums[0],因为最开始的最大子数组和只能是数组的第一个元素。
  3. 状态转移方程

    • 对于每个 i(从 1nums.length - 1),dp[i]dp[i-1] + nums[i]nums[i] 之间的较大值。这表示我们可以选择继续累加前面的子数组或者从当前位置重新开始一个新的子数组。
    • dp[i] = Math.max(dp[i - 1] + nums[i], nums[i])
  4. 计算顺序

    • dp[1] 开始计算,直到 dp[nums.length - 1]
  5. 答案

    • 最大子数组和是 dp 数组中的最大值。
class Solution {
    public int maxSubArray(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        
        int n = nums.length;
        int[] dp = new int[n];
        dp[0] = nums[0];
        int maxSum = dp[0];

        for (int i = 1; i < n; i++) {
            dp[i] = Math.max(dp[i - 1] + nums[i], nums[i]);
            maxSum = Math.max(maxSum, dp[i]);
        }

        return maxSum;
    }
}

我们首先处理了初始状态 dp[0],然后使用状态转移方程计算出每个位置的最大子数组和。同时,我们追踪 dp 数组中的最大值,这就是最大子数组和。这种方法比直接遍历所有可能的子数组要高效得多。

不同路径
问题描述

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。

问总共有多少条不同的路径?

示例:

img

输入:m = 3, n = 7
输出:28
动态规划的步骤
  1. 定义状态

    • 创建一个二维数组 dp,其中 dp[i][j] 表示到达网格中的位置 (i, j) 的不同路径数。
  2. 初始状态

    • dp[0][j] = 1 对于所有 j(从左上角到第一行的任何位置都只有一条路径,即全部向右移动)。
    • dp[i][0] = 1 对于所有 i(从左上角到第一列的任何位置都只有一条路径,即全部向下移动)。
  3. 状态转移方程

    • 对于每个 i > 0j > 0,机器人只能从上方或左方到达 (i, j),所以 dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
  4. 计算顺序

    • 按行(或按列)逐个计算 dp[i][j],从 dp[1][1] 开始,直到 dp[m - 1][n - 1]
  5. 答案

    • dp[m - 1][n - 1] 就是到达右下角的不同路径总数。
class Solution {
    public int uniquePaths(int m, int n) {
        int[][] dp = new int[m][n];

        // 初始化第一列和第一行
        for (int i = 0; i < m; i++) {
            dp[i][0] = 1;
        }
        for (int j = 0; j < n; j++) {
            dp[0][j] = 1;
        }

        // 动态规划填充其余的网格
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
            }
        }

        return dp[m - 1][n - 1];
    }
}

我们首先初始化了网格的第一行和第一列,因为这些位置的路径数是已知的。然后我们使用状态转移方程逐格计算其他位置的路径数。最后,dp[m - 1][n - 1] 给出了到达右下角的不同路径总数。

两类动态规划的解题思路对比

自顶向下(Top-Down)

也称为记忆化递归(Memoization)。

  1. 思路

    • 从原始问题开始,递归地解决所有子问题。
    • 利用记忆化存储(通常是数组或散列表),来记录已经解决的子问题的答案,避免重复计算。
  2. 实现细节

    • 通常使用递归方法实现。
    • 在函数调用时,先检查解是否已经在记忆化存储中。如果是,直接返回该解;如果不是,计算解并存储在记忆化存储中。
  3. 优缺点

    • 优点:递归实现更直观,更接近问题的实际定义。
    • 缺点:可能导致大量的递归调用,从而引起栈溢出;实现通常比自底向上慢。

自底向上(Bottom-Up)

也称为表格化(Tabulation)。

  1. 思路

    • 从最简单的子问题开始,逐步解决更复杂的子问题,直到解决最终问题。
    • 利用表(通常是数组)来按顺序存储每个子问题的解。
  2. 实现细节

    • 通常使用迭代方法实现。
    • 填充一个表格,表中的每个条目对应一个子问题的解。条目的填充顺序确保每个子问题在被解决之前,所有需要的信息都已经被计算并存储。
  3. 优缺点

    • 优点:通常比自顶向下快;避免了递归调用,降低了栈溢出的风险。
    • 缺点:可能较难理解和实现,特别是当问题的状态转移不是很直观时。

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

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

相关文章

OpenHarmony实战:用IPOP调试 OpenHarmony 内核

前言 我使用的是 IPOP V4.1&#xff0c;基于 OpenHarmony 开源系统和 RK3568 开发板&#xff0c;在 PC 上运行此软件&#xff0c;查看运行、错误日志来调试内核。作为网络、嵌入式式内核调试的必备工具&#xff0c;建议同学珍藏。IPOP 运行在 PC 上&#xff0c;操作系统是 Win…

LabVIEW动车组谐波分析与检测系统

LabVIEW动车组谐波分析与检测系统 随着中国高速铁路网络的快速发展&#xff0c;动车组数量和运行速度的不断提升&#xff0c;其产生的谐波问题对电网产生了不小的影响。基于图形化编程语言LabVIEW&#xff0c;开发了一套动车组谐波分析与检测系统&#xff0c;旨在实时监控与分…

华为数通方向HCIP-DataCom H12-821题库(多选题:241-260)

第241题 [RTAospf100 [RTA-ospf-100]silent-intefaceGigabitEthernet 1/0/0上面是路由器RTA的部分配置,对于此部分的配置描述,正确的是: A、接口gigabitethemet 1/0/0的直连路由仍然可以发布出去 B、无法与该接口的直连邻居形成邻居关系 C、禁止接口gigabi tethemet 1/0/0发…

AcrelEMS-EV 汽车制造能效管理系统解决方案

安科瑞电气股份有限公司 祁洁 15000363176 一、行业现状 1、政府、市场越来越关注碳排放指标。 2、用能设备缺乏完整的在线监视分析系统&#xff0c;无法及时发现用能异常和能源利用效率。 3、不能生产全流程监测和分析能源利用水平&#xff0c;无法及时发现浪费。 4、用…

Linux shell编程学习笔记45:uname命令-获取Linux系统信息

0 前言 linux 有多个发行版本&#xff0c;不同的版本都有自己的版本号。 如何知道自己使用的Linux的系统信息呢&#xff1f; 使用uname命令、hostnamectl命令&#xff0c;或者通过查看/proc/version文件来了解这些信息。 我们先看看uname命令。 1 uname 命令的功能和格式 …

4、jvm基础知识(四)

有哪些常见的垃圾回收算法&#xff1f; ⚫1960年John McCarthy发布了第一个GC算法&#xff1a;标记-清除算法。 ⚫1963年Marvin L. Minsky 发布了复制算法。 本质上后续所有的垃圾回收算法&#xff0c;都是在上述两种算法的基础上优化而来。 垃圾回收算法-标记清除算法 标记清…

3d在线虚拟数字展馆让学员通过游戏化体验接受爱国主义教育

随着科技的飞速发展&#xff0c;红色展厅已不再局限于实体空间。现在&#xff0c;借助VR虚拟仿真技术的强大力量&#xff0c;我们与多家党建馆推出一个全新的教育平台——VR红色虚拟展馆。在这里&#xff0c;爱国主题与尖端技术相结合&#xff0c;为广大学生提供一种全新的、互…

Excel·VBA二维数组组合函数之穷举推理题

看到一个帖子《CSDN-求助一道推理题》&#xff0c;与之前《python穷举暴力破解《2018年刑侦推理题》用python穷举的推理题很类似 那么是否可以使用《ExcelVBA二维数组组合函数、组合求和》combin_arr2d函数&#xff0c;生成结果进行穷举呢&#xff1f; Sub 穷举推理题()Dim …

sql注入---Union注入

文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 学习目标 了解union注入过程中用到的关键数据库&#xff0c;数据表&#xff0c;数据列sql查询中group_concat的作用使用union注入拿到靶机中数据库里的所有用户名和密码 一. 获得数据库表名和列…

Flask-RESTful 分析

Flask-RESTful 是一个 Flask 扩展&#xff0c;它为构建 RESTful API 提供了方便的工具和资源。它简化了创建 RESTful 服务的过程&#xff0c;允许开发者专注于业务逻辑而不是 HTTP 协议的细节。 资源&#xff08;Resources&#xff09;&#xff1a; Resource 类&#xff1a;是…

无代理方式的网络准入技术:保护泛终端企业网络安全的未来

云计算、大数据、物联网、移动化办公等技术的普及&#xff0c;打破了传统局域网的边界&#xff0c;通过各种方式连接到企业网络中的设备越来越多&#xff0c;如BYOD、IoT、OT等。企业在享受新技术带来的便利之际&#xff0c;也面临着更加多元化的安全威胁&#xff0c;如勒索病毒…

vscode 自用的一些配置

目录 1&#xff0c;修改默认配置1&#xff0c;关闭预览模式2&#xff0c;取消自动定位到左侧边栏 2&#xff0c;自定义快捷键1&#xff0c;手动定位到左侧边栏2&#xff0c;关闭其他3&#xff0c;其他常用快捷键 3&#xff0c;插件1&#xff0c;和 git 相关的GitlensGit Histor…

《2023腾讯云容器和函数计算技术实践精选集》--在 K8s 上跑腾讯云 Serverless 函数,打破传统方式造就新变革

目录 目录 前言 《2023腾讯云容器和函数计算技术实践精选集》带来的思考 1、特色亮点 2、阅读体验 3、实用建议 4、整体评价 Serverless 和 K8s 的优势 1、关于Serverless 函数的特点 2、K8s 的特点 腾讯云 Serverless 函数在 K8s 上的应用对企业服务的影响 案例分…

CrossOver玩游戏会损害电脑吗 CrossOver玩游戏会卡吗 Mac玩游戏 crossover24免费激活

CrossOver是一款可以在macOS上运行Windows应用程序的软件&#xff0c;它利用了Wine技术&#xff0c;无需安装虚拟机或双系统&#xff0c;可以直接在苹果系统下运行Windows游戏。那么&#xff0c;使用CrossOver玩游戏会损害电脑吗&#xff1f;CrossOver玩游戏会卡吗&#xff1f;…

设计模式23--观察者模式

定义 案例一 案例二 优缺点

Java 操作 Hadoop 集群之 HDFS 的应用案例详解

Java 操作 Hadoop 注意:本文内容基于 Hadoop 集群搭建完成基础上: Linux 系统 CentOS7 上搭建 Hadoop HDFS集群详细步骤 本文的内容是基于下面前提: Hadoop 集群搭建完成并能正常启动和访问Idea 和 Maven 分别安装完成需要有 JavaSE 基础和熟悉操作hadoop 的 hdfs dfs 命令…

Samtec连接器 | 应用分享C-V2X技术在汽车领域的应用

【前言】 在汽车设计领域有一个新的缩写&#xff0c;就是C-V2X。被谈及时&#xff0c;这被称为车辆到X&#xff0c;有时也被称为车辆到万物。前面的 "C "代表蜂窝网络。 这些缩写代表最新的基于车辆应用利用蜂窝通讯网络的电子产品。特别是&#xff0c;正在推出的5G…

Spring6-单元测试:JUnit

1. 概念 在进行单元测试时&#xff0c;特别是针对使用了Spring框架的应用程序&#xff0c;我们通常需要与Spring容器交互以获取被测试对象及其依赖。传统做法是在每个测试方法中手动创建Spring容器并从中获取所需的Bean。以下面的两行常见代码为例&#xff1a; ApplicationCo…

AWS迁移教程,Redis迁移到Elasticache

当企业不断出海拓展业务&#xff0c;面临的挑战之一就是如何高效迁移应用程序及数据库至云端。为解决这一问题&#xff0c;AWS云专门提供多种简单且高效的迁移方式&#xff0c;进行帮助企业实现应用程序的平稳迁移&#xff0c;从而降低迁移过程中的风险和成本。下面九河云将为大…

数据可视化基础与应用-07-数据可视化第二版各种类型图表的绘制优化版

参考教材截图 数据可视化第二版-03部分-06章-比较与排序 总结 本系列博客为基于《数据可视化第二版》一书的教学资源博客。本文主要是第6章&#xff0c;比较与排序可视化的案例相关。 可视化视角-比较与排序 代码实现 创建虚拟环境 我的conda下有多个python环境。 1. pyt…