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

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

基本概念

动态规划(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. 优缺点

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python | SLP | EOF | 去除季节趋势

EOF & PC 前言 在计算EOF&#xff08;经验正交函数&#xff09;之前去除季节循环是为了消除数据中的季节变化的影响&#xff0c;使得EOF能够更好地捕捉数据中的空间变化模式。如果不去除季节循环&#xff0c;季节性信号可能会在EOF中占据较大的比例&#xff0c;从而影响对其…

【Greenplum】GP库 too many clients already错误,重启失败问题解决方案

问题描述&#xff1a; 连接数满了后&#xff0c;导致 gp库无法连接了&#xff0c;通过登录服务器&#xff0c;使用gpadmin用户进行重启操作&#xff0c;也报too many clients already&#xff0c;无法重启。 采用 psql -d postgres -U gpadmin 连接库&#xff0c;也报too man…

C语言----数据在内存中的存储

文章目录 前言1.整数在内存中的存储2.大小端字节序和字节序判断2.1 什么是大小端&#xff1f;2.2 练习 3.浮点数在内存中的存储3.1.引子3.2.浮点数的存储3.2.2 浮点数取的过程 前言 下面给大家介绍一下数据在内存中的存储&#xff0c;这个是一个了解c语言内部的知识点&#xf…

element-ui breadcrumb 组件源码分享

今日简单分享 breadcrumb 组件的源码实现&#xff0c;主要从以下三个方面&#xff1a; 1、breadcrumb 组件页面结构 2、breadcrumb 组件属性 3、breadcrumb 组件 slot 一、breadcrumb 组件页面结构 二、breadcrumb 组件属性 2.1 separator 属性&#xff0c;分隔符&#xff…

Golang | Leetcode Golang题解之第10题正则表达式匹配

题目&#xff1a; 题解&#xff1a; func isMatch(s string, p string) bool {m, n : len(s), len(p)matches : func(i, j int) bool {if i 0 {return false}if p[j-1] . {return true}return s[i-1] p[j-1]}f : make([][]bool, m 1)for i : 0; i < len(f); i {f[i] m…

python--IO流和字符流的写入写出

1.IO流&#xff1a;&#xff08;input output stream&#xff09; python的IO流只有一个函数&#xff1a;open函数 属性不用带括号&#xff1b;方法通通要带括号 输入输出流&#xff1a;狭义上来说&#xff0c;指的就是内存数据和磁盘这种可以永久 存储数据的设备 IO流 IO流…

[C#]OpenCvSharp使用HoughCircles霍夫圆检测算法找出圆位置并计数

【效果展示】 原图&#xff1a; 找出位置&#xff1a; 【测试环境】 vs2019,netframework4.7.2,opencvsharp4.8.0 【函数用法】 cv2提供了一种圆检测的方法&#xff1a;HoughCircles。该函数的返回结果与参数设置有很大的关系。 检测的图像时9枚钱币&#xff0c;分别使用了…

Codeforces Round 931 (Div. 2) ---- E. Weird LCM Operations ---- 题解

E. Weird LCM Operations&#xff1a; 题目大意&#xff1a; 思路解析&#xff1a; 这是一道构造题&#xff0c;那么观察这个构造有啥性质&#xff0c;观察到最多操作次数为 n/6 5&#xff0c;然后每次操作需要选择三个数&#xff0c;如果每次操作的三个数都不和之前的重复的…

算法-最值问题

#include<iostream> using namespace std; int main() {int a[7];//上午上课时间int b[7];//下午上课时间int c[7];//一天总上课时间for (int i 0; i < 7; i) {cin >> a[i] >> b[i];c[i] a[i] b[i];}int max c[0];//max记录最长时间int index -1;//索…

解决 VSCode 编辑器点击【在集成终端中打开】出现新的弹框

1、问题描述 在 VSCode 的项目下&#xff0c;鼠标右键&#xff0c;点击【在集成终端中打开】&#xff0c;出现新的一个弹框。新版的 VSCode 会有这个问题&#xff0c;一般来说我们都希望终端是在 VSCode 的控制台中打开的&#xff0c;那么如何关闭这个弹框呢&#xff1f; 2、解…

震惊!!原来阻塞队列消息队列这样理解会更简单!!!

震惊!!原来阻塞队列&&消息队列这样理解会更简单!!! 一:阻塞队列二:消息队列2.1:生产者消费者模型2.1.1:解耦合:2.1.2:削峰填谷: 三:消息队列代码3.1.13.1.2:3.1.3:生产慢,消费快,消费阻塞3.1.3:生产快,消费慢,生产阻塞 二级目录二级目录 一:阻塞队列 阻塞队列:先进先出…

能耗监测管理系统与技术方案

能耗监测管理系统是目前能源管理中重要的技术手段&#xff0c;它通过集成现代监测技术和信息技术&#xff0c;实现对能源消耗的实时监控、数据分析和决策支持&#xff0c;帮助企业或机构实现能源的高效管理和节能降耗。本篇文章将从能耗监测管理系统的组成、关键技术、应用领域…

数据库重点知识(个人整理笔记)

目录 1. 索引是什么&#xff1f; 1.1. 索引的基本原理 2. 索引有哪些优缺点&#xff1f; 3. MySQL有哪几种索引类型&#xff1f; 4. mysql聚簇和非聚簇索引的区别 5. 非聚簇索引一定会回表查询吗&#xff1f; 6. 讲一讲前缀索引&#xff1f; 7. 为什么索引结构默认使用B…

[技术闲聊]我对电路设计的理解(三)

终于可以独立做项目了&#xff0c;是不是很激动&#xff0c;是不是为自己骄傲和自豪&#xff0c;应该的&#xff0c;奋斗那么久不就是为了站在山巅看看四周的风景嘛&#xff01; 虽说山外还有山&#xff0c;但是此刻就在脚下的山巅上&#xff0c;怡然自得都是不过分的&#xff…

黑马点评part1 -- 短信登录

目录 1 . 导入项目 : 2 . 基于Session实现短信验证登录 2 . 1 原理 : 2 . 2 发送短信验证码 : 2 . 3 短信验证码登录和验证功能 : 2 . 4 登录验证功能 2 . 5 隐藏用户敏感信息 2 . 6 session共享问题 2 . 7 Redis 代替 session 2 . 8 基于Redis实现短信登录 UserS…

FressRTOS_day4:2024/4/4

1.总结二进制信号量和计数型信号量的区别&#xff0c;以及他们的使用场景。 二进制信号量的数值只有0和1。&#xff08;用于共享资源的访问&#xff09;&#xff1b;而计数型信号量的值一般是大于或者等于2&#xff08;用于生产者和消费者模型&#xff09; 2.使用计数型信号量…

JAVAEE——文件IO

文章目录 文件的概念什么是文件&#xff1f;树型结构组织 和 目录文件路径相对路径绝对路径 文件的分类文件的权限 文件读写IO API字符流操作API 警告字节流操作APIInputStreamOutputStream 文件的概念 什么是文件&#xff1f; 我们先来理解一下什么是文件&#xff0c;那么想…

【C++常用函数介绍】isalpha,isalnum、isdigit、islower、isupper 用法

首先 isalpha,isalnum、isdigit、islower、isupper 的使用方法都需要用到一个头文件 #include<ctype.h>其次 系统的介绍以上函数的用法 isalpha()用来判断一个字符是否为字母 isalnum&#xff08;&#xff09;用来判断一个字符是否为数字或者字母&#xff0c;也就是说…

四川尚熠电子商务有限公司靠谱吗?怎么样?

在当下数字化浪潮中&#xff0c;电子商务行业正以前所未有的速度蓬勃发展。四川尚熠电子商务有限公司&#xff0c;作为专注于抖音电商服务的企业&#xff0c;凭借其敏锐的市场洞察力和创新精神&#xff0c;正成为行业内的佼佼者&#xff0c;为众多品牌打开抖音电商市场的大门。…

Stable Diffusion扩散模型【详解】小白也能看懂!!

文章目录 1、Diffusion的整体过程2、加噪过程2.1 加噪的具体细节2.2 加噪过程的公式推导 3、去噪过程3.1 图像概率分布 4、损失函数5、 伪代码过程 此文涉及公式推导&#xff0c;需要参考这篇文章&#xff1a; Stable Diffusion扩散模型推导公式的基础知识 1、Diffusion的整体…