算法修炼之筑基篇——筑基二层中期(讨论一下如何解决动态方程问题,没时间了,快快快看一下)

博主:命运之光

专栏:算法修炼之练气篇

专栏:算法修炼之筑基篇

博主的其他文章:点击进入博主的主页

前言:学习了算法修炼之练气篇想必各位蒟蒻们的基础已经非常的扎实了,下来我们进阶到算法修炼之筑基篇的学习。筑基期和练气期难度可谓是天差地别,懂得都懂,题目难度相比起练气期的题目难度提升很多,所以要是各位蒟蒻小伙伴们看不懂筑基期的题目可以在练气期多积累积累,练气期的题目也会不断更新,大家一定要把基础打牢固了再来看筑基期的题目哈,这样子也可以提高大家的学习效率,一举两得,加油(●'◡'●)

目录

✨动态规划问题一般怎么解决?

✨有没有什么套路?

✨学好动态规划要学会那些基本内容

✨一些动态规划题的解题思路和标准模板,和通用状态方程

最长公共子序列(Longest Common Subsequence):

最长递增子序列(Longest Increasing Subsequence):

最大子数组和(Maximum Subarray Sum):

矩阵链相乘(Matrix Chain Multiplication):

最短路径问题(Shortest Path Problem):

切割钢条问题(Cutting Rod Problem):

背包问题的变种:

字符串编辑距离(Edit Distance):

✨结语


✨动态规划问题一般怎么解决?

动态规划(Dynamic Programming)是一种解决优化问题的算法思想,通常用于解决具有重叠子问题性质和最优子结构性质的问题。动态规划将问题分解成一系列重叠的子问题,并通过保存子问题的解来避免重复计算,从而提高算法的效率。

下面是一般解决动态规划问题的步骤:

1. 确定问题的状态:将原问题划分为若干个子问题,确定每个子问题的状态,状态一般由一些变量表示。

2. 定义状态转移方程:根据子问题之间的关系,建立状态之间的转移方程。这个转移方程描述了问题的最优解与子问题的最优解之间的关系。

3. 初始化:确定初始状态的值,一般是边界情况下的解,或者根据题目要求进行初始化。

4. 确定计算顺序:根据状态转移方程,确定计算状态的顺序。通常,需要先计算较小规模的子问题,再逐步计算规模较大的子问题。

5. 递推计算:按照计算顺序,通过状态转移方程逐步计算每个状态的值。

6. 求解原问题:根据子问题的解或最终状态的值,求解原问题的最优解。

7. 可选的优化:有些情况下,可以通过一些技巧进行进一步的优化,例如使用滚动数组、空间压缩等方法来降低空间复杂度。

总的来说,动态规划是一种自底向上的求解方法,通过将原问题拆解为子问题并利用子问题的解来求解原问题的最优解。以上是一般解决动态规划问题的步骤,具体问题需要根据实际情况进行调整和优化。

✨有没有什么套路?

在使用C/C++编写动态规划算法时,以下是一些常见的套路和技巧:

  1. 定义数组:通常情况下,动态规划算法需要定义一个二维数组或一维数组来保存子问题的解。根据问题的需要,选择合适的数组类型(如int、long long等)和大小。
  2. 初始化数组:根据问题的具体要求,对数组进行初始化。有些情况下,可以通过将数组的初始值设置为一个特殊值来标记状态为未计算或无效。
  3. 状态转移方程的实现:根据问题的状态转移方程,使用循环结构(如for循环)遍历数组,逐个计算每个状态的值。注意循环的起始位置和结束条件。
  4. 边界情况的处理:某些情况下,需要特别处理边界状态的值,例如将边界状态初始化为已知的值,或者单独处理边界状态的转移方程。
  5. 循环顺序的选择:根据问题的性质,选择合适的循环顺序。有时可以从小规模子问题开始,逐渐扩大规模;有时可以从大规模问题开始,逐步缩小规模。
  6. 空间优化:有时动态规划算法会占用较大的空间,可以考虑使用滚动数组(滑动窗口)等技巧来减少内存使用。
  7. 返回结果:根据问题的要求,返回计算得到的最优解或所需的结果。

总的来说,C/C++编写动态规划算法时,需要熟悉数组的定义和操作,灵活运用循环结构和条件语句,并注意处理边界情况和选择合适的循环顺序。对于大规模问题,可能需要考虑空间优化技巧。根据问题的具体要求,进行相应的算法设计和实现。

✨学好动态规划要学会那些基本内容

  1. 最优子结构:了解最优子结构的概念,即问题的最优解可以由子问题的最优解推导而来。
  2. 重叠子问题:理解重叠子问题的性质,即问题的求解过程中存在重复计算的子问题。
  3. 状态定义:学会确定问题的状态,将问题划分为子问题,并明确每个子问题的状态表示。
  4. 状态转移方程:学会建立子问题之间的转移关系,即确定状态之间的转移方程,描述问题的最优解与子问题的最优解之间的关系。
  5. 初始化:了解如何对问题的初始状态进行初始化,确定边界条件下的解。
  6. 计算顺序:学会确定计算状态的顺序,一般是从较小规模的子问题逐步计算到规模较大的子问题。
  7. 递推计算:熟悉利用状态转移方程进行递推计算的方法,通过保存子问题的解来避免重复计算。
  8. 求解原问题:掌握如何根据子问题的解或最终状态的值,求解原问题的最优解。
  9. 空间优化:了解一些空间优化的技巧,例如滚动数组、状态压缩等,以减少算法的内存使用。
  10. 练习和实践:动态规划需要通过大量的练习和实践来巩固和提高技能,因为每个问题都有不同的特点和解题思路。

总的来说,学好动态规划需要理解其基本概念和思想,并在实践中不断积累经验。通过解决各种不同类型的动态规划问题,可以提高对动态规划算法的理解和应用能力。

✨一些动态规划题的解题思路和标准模板,和通用状态方程

最长公共子序列(Longest Common Subsequence):

  • 解题思路:使用动态规划来解决最长公共子序列问题,通常需要构建一个二维数组来保存子问题的解。从左上角开始,逐步计算每个位置的值,根据字符的匹配情况进行状态转移。
  • 通用状态方程:dp[i][j]表示字符串A的前i个字符和字符串B的前j个字符的最长公共子序列长度。
  • 标准模板:
int longestCommonSubsequence(string text1, string text2) {
    int m = text1.length();
    int n = text2.length();
    vector> dp(m + 1, vector(n + 1, 0));
    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= n; j++) {
            if (text1[i - 1] == text2[j - 1]) {
                dp[i][j] = dp[i - 1][j - 1] + 1;
            } else {
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
    }
    return dp[m][n];
}

最长递增子序列(Longest Increasing Subsequence):

  • 解题思路:使用动态规划来解决最长递增子序列问题,通常需要构建一个一维数组来保存子问题的解。遍历数组,逐个计算每个位置的最长递增子序列长度,并更新结果。
  • 通用状态方程:dp[i]表示以第i个元素结尾的最长递增子序列长度。
  • 标准模板:
int lengthOfLIS(vector& nums) {
    int n = nums.size();
    vector dp(n, 1);
    int maxLen = 1;
    for (int i = 1; i < n; i++) {
        for (int j = 0; j < i; j++) {
            if (nums[i] > nums[j]) {
                dp[i] = max(dp[i], dp[j] + 1);
            }
        }
        maxLen = max(maxLen, dp[i]);
    }
    return maxLen;
}

最大子数组和(Maximum Subarray Sum):

  • 解题思路:使用动态规划来解决最大子数组和问题,通常需要维护一个变量来保存当前位置的最大子数组和,并不断更新。
  • 通用状态方程:dp[i]表示以第i个元素结尾的最大子数组和。
  • 标准模板:
int maxSubArray(vector& nums) {
    int n = nums.size();
    vector dp(n, 0);
    dp[0] = nums[0];
    int maxSum = dp[0];
    for (int i = 1; i < n; i++) {
        dp[i] = max(nums[i], dp[i - 1] + nums[i]);
        maxSum = max(maxSum, dp[i]);
    }
    return maxSum;
}

矩阵链相乘(Matrix Chain Multiplication):

  • 解题思路:矩阵链相乘问题可以使用动态规划来解决。需要构建一个二维数组来保存子问题的最优解,从小规模的子问题开始逐步计算,然后根据状态转移方程更新最优解。
  • 通用状态方程:dp[i][j]表示从第i个矩阵到第j个矩阵相乘的最小代价。
  • 标准模板:
int matrixChainMultiplication(vector& dimensions) {
    int n = dimensions.size();
    vector> dp(n, vector(n, 0));
    for (int len = 2; len < n; len++) {
        for (int i = 0; i < n - len; i++) {
            int j = i + len;
            dp[i][j] = INT_MAX;
            for (int k = i + 1; k < j; k++) {
                int cost = dp[i][k] + dp[k][j] + dimensions[i] * dimensions[k] * dimensions[j];
                dp[i][j] = min(dp[i][j], cost);
            }
        }
    }
    return dp[0][n - 1];
}

最短路径问题(Shortest Path Problem):

  • 解题思路:最短路径问题可以使用动态规划来解决,常用的算法是Dijkstra算法和Floyd-Warshall算法。这些算法通过构建一个二维数组来保存子问题的最优解,从小规模的子问题开始逐步计算,然后根据状态转移方程更新最优解。
  • 通用状态方程:dp[i][j]表示从顶点i到顶点j的最短路径长度。
  • 标准模板:
void shortestPath(vector>& graph) {
    int n = graph.size();
    vector> dp(graph);
    for (int k = 0; k < n; k++) {
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (dp[i][k] != INT_MAX && dp[k][j] != INT_MAX) {
                    dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]);
                }
            }
        }
    }
}

切割钢条问题(Cutting Rod Problem):

  • 解题思路:切割钢条问题可以使用动态规划来解决。通常使用自底向上的方法,从小段长度开始逐步计算最优解,然后根据状态转移方程更新最优解。
  • 通用状态方程:dp[i]表示长度为i的钢条的最大价值。
  • 标准模板:
int cuttingRod(vector& prices, int length) {
    int n = prices.size();
    vector dp(length + 1, 0);
    for (int i = 1; i <= length; i++) {
        for (int j = 1; j <= i; j++) {
            dp[i] = max(dp[i], prices[j - 1] + dp[i - j]);
        }
    }
    return dp[length];
}

背包问题的变种:

  • 解题思路:背包问题有多种变种,例如分组背包、二维背包等。每种变种问题的解题思路和状态方程都有所不同,需要根据具体问题进行分析和设计。通常也是通过构建一个二维或多维数组来保存子问题的解,然后根据不同的状态转移方程更新最优解。
  • 通用状态方程:根据具体问题的不同而不同。
  • 解题模板:根据具体问题的不同而不同可以看之前的背包问题的文章。

字符串编辑距离(Edit Distance):

  • 解题思路:字符串编辑距离问题可以使用动态规划来解决。通常构建一个二维数组来保存子问题的最优解,从小规模的子问题开始逐步计算,然后根据状态转移方程更新最优解。
  • 通用状态方程:dp[i][j]表示将字符串A的前i个字符转换为字符串B的前j个字符所需的最小编辑操作次数。
  • 标准模板:
int editDistance(string word1, string word2) {
    int m = word1.length();
    int n = word2.length();
    vector> dp(m + 1, vector(n + 1, 0));
    for (int i = 1; i <= m; i++) {
        dp[i][0] = i;
    }
    for (int j = 1; j <= n; j++) {
        dp[0][j] = j;
    }
    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= n; j++) {
            if (word1[i - 1] == word2[j - 1]) {
                dp[i][j] = dp[i - 1][j - 1];
            } else {
                dp[i][j] = min({dp[i - 1][j - 1], dp[i][j - 1], dp[i - 1][j]}) + 1;
            }
        }
    }
    return dp[m][n];
}

✨结语

动态规划是一种令人兴奋和有趣的算法思想,通过将问题分解为子问题并保存子问题的解,我们可以高效地求解复杂问题的最优解。在本篇博客中,我们探讨了几种常见的动态规划问题,包括最长公共子序列、最长递增子序列、最大子数组和、矩阵链相乘、最短路径问题、切割钢条问题和字符串编辑距离。对于每个问题,我们提供了解题思路、通用状态方程和标准模板。

希望这篇博客能够让你对动态规划有一个简单而全面的了解,并且激发你在解决问题时尝试动态规划的勇气。记住,在解决动态规划问题时,关键是理解问题的性质和定义状态,然后设计合适的状态转移方程。通过不断学习和练习,你将掌握动态规划的技巧,能够灵活地应对各种问题。

让我们一起享受动态规划的大冒险吧!愿你在解题过程中充满欢乐和成就感。继续保持好奇心,不断探索新的解题方法和技巧,你将在编程的旅程中取得更多的成就。

感谢你的阅读,希望这篇博客对你有所帮助。祝你在动态规划的世界中玩得开心,继续热爱编程!

你可能感兴趣的:(算法修炼之筑基篇,算法,动态规划,数据结构,蓝桥杯,C\C++)