动态规划的常用状态转移方程总结

动态规划的常用状态转移方程总结

文章目录

  • 动态规划的常用状态转移方程总结
    • 1.斐波那契数列
        • 1. 斐波那契数列定义
        • 2. 动态规划方程
        • 2.爬楼梯问题
            • 1. 爬楼梯问题定义
            • 2.动态规划方程
            • 3.背包问题
                • 1. 背包问题定义
                • 2. 动态规划方程
                • 4.最长递增子序列
                    • 1. 最长递增子序列定义
                    • 2. 动态规划方程
                    • 5.最大子数组和
                        • 1. 最大子数组和定义
                        • 2. 动态规划方程
                        • 6.最长公共子序列
                            • 1. 最长公共子序列定义
                            • 2. 动态规划方程
                            • 7.编辑距离
                                • 1. 编辑距离定义
                                • 2. 动态规划方程
                                • 8.打家劫舍
                                    • 1. 打家劫舍问题定义
                                    • 2. 动态规划方程
                                    • 9.最大正方形
                                        • 1. 最大正方形定义
                                        • 2. 动态规划方程

                                          1.斐波那契数列

                                          1. 斐波那契数列定义

                                          斐波那契数列是一个经典的数学数列,其中每个数字是前两个数字的和。通常,斐波那契数列的前几个数字是:1, 1, 2, 3, 5, 8, 13, 21, …

                                          2. 动态规划方程

                                          动态规划的转移方程为 dp[i] = dp[i-1] + dp[i-2],其中 dp[i] 表示第 i 个斐波那契数。

                                          // 定义一个函数,接受斐波那契数列的序号作为参数
                                          function fibonacci(n) { // 初始化一个数组dp,用于存储斐波那契数列的值
                                              const dp = [];
                                              // 初始条件:第0个和第1个斐波那契数都为1
                                              dp[0] = dp[1] = 1;
                                              // 通过动态规划的转移方程计算每个斐波那契数
                                              for (let i = 2; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2];
                                              }
                                              // 返回第n个斐波那契数
                                              return dp[n];
                                          }
                                          // 测试例子
                                          const n = 5;
                                          const result = fibonacci(n);
                                          console.log(`第${n}个斐波那契数为:${result}`); 
                                          //第5个斐波那契数为:8 数组[5]  1 1 2 3 5 8
                                          
                                          • fibonacci 函数接受一个参数 n,表示要计算第 n 个斐波那契数。
                                          • 初始化一个数组 dp,用于存储斐波那契数列的值。
                                          • 初始条件设置 dp[0] 和 dp[1] 均为 1,作为数列的起始。
                                          • 通过循环计算每个斐波那契数,根据动态规划的转移方程 dp[i] = dp[i-1] + dp[i-2]。
                                          • 最后返回第 n 个斐波那契数。

                                            2.爬楼梯问题

                                            1. 爬楼梯问题定义

                                            爬楼梯问题是一个经典的动态规划问题,其目标是计算爬到第 n 级楼梯的方法数,每次可以爬 1 或 2 级楼梯。动态规划的转移方程为 dp[i] = dp[i-1] + dp[i-2],其中 dp[i] 表示爬到第 i 级楼梯的方法数。

                                            2.动态规划方程

                                            dp[i] = dp[i-1] + dp[i-2],其中 dp[i] 表示爬到第 i 级楼梯的方法数。

                                            // 定义一个函数,接受楼梯的级数作为参数
                                            function climbStairs(n) { // 初始化一个数组dp,用于存储到达每一级楼梯的方法数
                                                const dp = [];
                                                // 初始条件:爬到第0级和第1级楼梯的方法数都为1
                                                dp[0] = dp[1] = 1;
                                                // 通过动态规划的转移方程计算每一级楼梯的方法数
                                                for (let i = 2; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2];
                                                }
                                                // 返回爬到第n级楼梯的方法数
                                                return dp[n];
                                            }
                                            // 测试例子
                                            const n = 4;
                                            const result = climbStairs(n);
                                            console.log(`爬到第${n}级楼梯的方法数为:${result}`);  
                                            //爬到第4级楼梯的方法数为:5
                                            当 n=4 时,输出结果是 5,表示爬到第 4 级楼梯有 5 种不同的方式。这包括:
                                            1 步 + 1 步 + 1 步 + 1 步
                                            2 步 + 1 步 + 1 步
                                            1 步 + 2 步 + 1 步
                                            1 步 + 1 步 + 2 步
                                            2 步 + 2 步
                                            
                                            • climbStairs 函数接受一个参数 n,表示楼梯的级数。
                                            • 初始化一个数组 dp,用于存储到达每一级楼梯的方法数。
                                            • 初始条件设置 dp[0] 和 dp[1] 均为 1,作为楼梯的起始条件。
                                            • 通过循环计算每一级楼梯的方法数,根据动态规划的转移方程 dp[i] = dp[i-1] + dp[i-2]。
                                            • 最后返回爬到第 n 级楼梯的方法数。

                                              3.背包问题

                                              1. 背包问题定义

                                              背包问题是一个经典的动态规划问题,目标是在给定背包容量下,选择一定数量的物品放入背包,使得放入背包的物品总价值最大。这里采用的是 0/1 背包问题,即每个物品只能选择放入一次或不放入。

                                              2. 动态规划方程

                                              动态规划的转移方程为 dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]] + value[i]),其中 dp[i][j] 表示在前 i 个物品中选择总重量不超过 j 的最大价值,weight[i] 表示第 i 个物品的重量,value[i] 表示第 i 个物品的价值。

                                              // 定义一个函数,接受物品的重量数组和价值数组以及背包的容量作为参数
                                              function knapsackProblem(weights, values, capacity) { const n = weights.length; // 物品的数量
                                                  // 初始化一个二维数组dp,表示在前i个物品中选择总重量不超过j的最大价值
                                                  const dp = new Array(n + 1).fill(0).map(() => new Array(capacity + 1).fill(0));
                                                  // 动态规划,计算每个子问题的最优解
                                                  for (let i = 1; i <= n; i++) { for (let j = 1; j <= capacity; j++) { // 当前物品的重量
                                                          const currentWeight = weights[i - 1];
                                                          // 当前物品的价值
                                                          const currentValue = values[i - 1];
                                                          // 如果当前物品的重量小于等于背包的容量
                                                          if (currentWeight <= j) { // 选择当前物品或不选择当前物品,取较大的价值
                                                              dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - currentWeight] + currentValue);
                                                          } else { // 如果当前物品的重量大于背包的容量,则不能选择当前物品
                                                              dp[i][j] = dp[i - 1][j];
                                                          }
                                                      }
                                                  }
                                                  // 返回在前n个物品中选择总重量不超过capacity的最大价值
                                                  return dp[n][capacity];
                                              }
                                              // 测试例子
                                              const weights = [2, 1, 3];
                                              const values = [4, 2, 3];
                                              const capacity = 4;
                                              const result = knapsackProblem(weights, values, capacity);
                                              console.log(`在给定容量为${capacity}的背包中,能够获得的最大价值为:${result}`);  
                                              //在给定容量为4的背包中,能够获得的最大价值为:6
                                              
                                              • knapsackProblem 函数接受三个参数:物品的重量数组 weights、价值数组 values,以及背包的容量 capacity。
                                              • 初始化一个二维数组 dp,用于存储在前 i 个物品中选择总重量不超过 j 的最大价值。
                                              • 通过嵌套循环计算每个子问题的最优解,根据动态规划的转移方程更新 dp 数组。
                                              • 最后返回在前 n 个物品中选择总重量不超过 capacity 的最大价值。

                                                4.最长递增子序列

                                                1. 最长递增子序列定义

                                                最长递增子序列是指在一个数组中找到一个子序列,使得这个子序列中的元素是按照升序排列的,并且在所有符合条件的子序列中它是最长的。

                                                2. 动态规划方程

                                                动态规划的转移方程为 dp[i] = max(dp[j] + 1, dp[i]),其中 dp[i] 表示以第 i 个元素结尾的最长递增子序列的长度,j 为 0 到 i-1 的索引,且 nums[i] > nums[j]。

                                                // 定义一个函数,接受数组作为参数
                                                function longestIncreasingSubsequence(nums) { const n = nums.length; // 数组的长度
                                                    // 初始化一个数组dp,表示以第i个元素结尾的最长递增子序列的长度
                                                    const dp = new Array(n).fill(1);
                                                    // 动态规划,计算每个子问题的最优解
                                                    for (let i = 1; i < n; i++) { for (let j = 0; j < i; j++) { // 如果当前元素大于前面某个元素,则更新最长递增子序列的长度
                                                            if (nums[i] > nums[j]) { dp[i] = Math.max(dp[j] + 1, dp[i]);
                                                            }
                                                        }
                                                    }
                                                    // 返回最长递增子序列的最大长度
                                                    return Math.max(...dp);
                                                }
                                                // 测试例子
                                                const nums = [10, 22, 9, 33, 21, 50, 41, 60, 80];
                                                const result = longestIncreasingSubsequence(nums);
                                                console.log(`最长递增子序列的长度为:${result}`);  
                                                //最长递增子序列的长度为:6
                                                
                                                • longestIncreasingSubsequence 函数接受一个数组 nums 作为参数。
                                                • 初始化一个数组 dp,用于存储以每个元素结尾的最长递增子序列的长度,初始值为1。
                                                • 动态规划通过两层循环计算每个子问题的最优解。
                                                • 如果当前元素 nums[i] 大于前面某个元素 nums[j],则更新最长递增子序列的长度 dp[i] 为当前的最大值。
                                                • 最后返回最长递增子序列的最大长度,使用 Math.max(...dp) 获取数组中的最大值。

                                                  5.最大子数组和

                                                  1. 最大子数组和定义

                                                  最大子数组和问题是一个经典的动态规划问题,其目标是找到一个具有最大和的连续子数组。

                                                  2. 动态规划方程

                                                  动态规划的转移方程为 dp[i] = max(nums[i], nums[i] + dp[i-1]),其中 dp[i] 表示以第 i 个元素结尾的最大子数组和。

                                                  // 定义一个函数,接受数组作为参数
                                                  function maxSubArray(nums) { const n = nums.length; // 数组的长度
                                                      // 初始化一个数组dp,表示以第i个元素结尾的最大子数组和
                                                      const dp = new Array(n);
                                                      // 初始条件:第0个元素结尾的最大子数组和即为第0个元素本身
                                                      dp[0] = nums[0];
                                                      // 动态规划,计算每个子问题的最优解
                                                      for (let i = 1; i < n; i++) { // 当前元素单独构成子数组,或者与前面的子数组连接
                                                          dp[i] = Math.max(nums[i], nums[i] + dp[i - 1]);
                                                      }
                                                      // 返回以每个元素结尾的最大子数组和中的最大值
                                                      return Math.max(...dp);
                                                  }
                                                  // 测试例子
                                                  const nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4];
                                                  const result = maxSubArray(nums);
                                                  console.log(`最大子数组和为:${result}`);  
                                                  //最大子数组和为:6
                                                  
                                                  • maxSubArray 函数接受一个数组 nums 作为参数。
                                                  • 初始化一个数组 dp,用于存储以每个元素结尾的最大子数组和。
                                                  • 初始条件设置 dp[0] 为第0个元素本身。
                                                  • 动态规划通过循环计算每个子问题的最优解,即以当前元素结尾的最大子数组和。
                                                  • 转移方程 dp[i] = max(nums[i], nums[i] + dp[i-1]) 表示当前元素单独构成子数组,或者与前面的子数组连接,取两者中的较大值。
                                                  • 最后返回以每个元素结尾的最大子数组和中的最大值,使用 Math.max(...dp) 获取数组中的最大值。

                                                    6.最长公共子序列

                                                    1. 最长公共子序列定义

                                                    最长公共子序列是指在两个字符串中找到一个最长的子序列,使得它在两个字符串中都出现,但不要求连续。

                                                    2. 动态规划方程

                                                    动态规划的转移方程为:

                                                    • 如果 str1[i] 等于 str2[j],则 dp[i][j] = dp[i-1][j-1] + 1;
                                                    • 否则,dp[i][j] = max(dp[i-1][j], dp[i][j-1]),其中 dp[i][j] 表示 str1 的前 i 个字符和 str2 的前 j 个字符的最长公共子序列的长度。
                                                      // 定义一个函数,接受两个字符串作为参数
                                                      function longestCommonSubsequence(str1, str2) { const m = str1.length; // 字符串1的长度
                                                          const n = str2.length; // 字符串2的长度
                                                          // 初始化一个二维数组dp,表示str1的前i个字符和str2的前j个字符的最长公共子序列的长度
                                                          const dp = new Array(m + 1).fill(0).map(() => new Array(n + 1).fill(0));
                                                          // 动态规划,计算每个子问题的最优解
                                                          for (let i = 1; i <= m; i++) { for (let j = 1; j <= n; j++) { // 如果当前字符相等,则取左上方的值加1
                                                                  if (str1[i - 1] === str2[j - 1]) { dp[i][j] = dp[i - 1][j - 1] + 1;
                                                                  } else { // 如果当前字符不相等,则取左方和上方的最大值
                                                                      dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
                                                                  }
                                                              }
                                                          }
                                                          // 返回str1的前m个字符和str2的前n个字符的最长公共子序列的长度
                                                          return dp[m][n];
                                                      }
                                                      // 测试例子
                                                      const str1 = "abcde";
                                                      const str2 = "ace";
                                                      const result = longestCommonSubsequence(str1, str2);
                                                      console.log(`最长公共子序列的长度为:${result}`); 
                                                      //最长公共子序列的长度为:3
                                                      
                                                      • longestCommonSubsequence 函数接受两个字符串 str1 和 str2 作为参数。
                                                      • 初始化一个二维数组 dp,用于存储str1的前i个字符和str2的前j个字符的最长公共子序列的长度。
                                                      • 动态规划通过两层循环计算每个子问题的最优解。
                                                      • 转移方程中,如果当前字符相等,则取左上方的值加1;如果当前字符不相等,则取左方和上方的最大值。
                                                      • 最后返回 str1 的前 m 个字符和 str2 的前 n 个字符的最长公共子序列的长度,即 dp[m][n]。

                                                        7.编辑距离

                                                        1. 编辑距离定义

                                                        编辑距离是衡量两个字符串相似程度的一种指标,表示将一个字符串转换成另一个字符串所需的最少操作次数。典型的操作包括插入一个字符、删除一个字符、替换一个字符。

                                                        2. 动态规划方程

                                                        动态规划的转移方程为:

                                                        • 如果 word1[i] 等于 word2[j],则 dp[i][j] = dp[i-1][j-1];
                                                        • 否则,dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1,其中 dp[i][j] 表示将 word1 的前 i 个字符转换为 word2 的前 j 个字符所需的最少操作次数。
                                                          // 定义一个函数,接受两个单词作为参数
                                                          function minDistance(word1, word2) { const m = word1.length; // 单词1的长度
                                                              const n = word2.length; // 单词2的长度
                                                              // 初始化一个二维数组dp,表示将word1的前i个字符转换为word2的前j个字符所需的最少操作次数
                                                              const dp = new Array(m + 1).fill(0).map(() => new Array(n + 1).fill(0));
                                                              // 初始化边界条件
                                                              for (let i = 0; i <= m; i++) { dp[i][0] = i;
                                                              }
                                                              for (let j = 0; j <= n; j++) { dp[0][j] = j;
                                                              }
                                                              // 动态规划,计算每个子问题的最优解
                                                              for (let i = 1; i <= m; i++) { for (let j = 1; j <= n; j++) { // 如果当前字符相等,则取左上方的值
                                                                      if (word1[i - 1] === word2[j - 1]) { dp[i][j] = dp[i - 1][j - 1];
                                                                      } else { // 如果当前字符不相等,则取左方、上方和左上方的最小值加1
                                                                          dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) + 1;
                                                                      }
                                                                  }
                                                              }
                                                              // 返回将word1的前m个字符转换为word2的前n个字符所需的最少操作次数
                                                              return dp[m][n];
                                                          }
                                                          // 测试例子
                                                          const word1 = "horse";
                                                          const word2 = "ros";
                                                          const result = minDistance(word1, word2);
                                                          console.log(`将"${word1}"转换为"${word2}"所需的最少操作次数为:${result}`); 
                                                          //将"horse"转换为"ros"所需的最少操作次数为:3
                                                          
                                                          • minDistance 函数接受两个单词 word1 和 word2 作为参数。
                                                          • 初始化一个二维数组 dp,用于表示将 word1 的前 i 个字符转换为 word2 的前 j 个字符所需的最少操作次数。
                                                          • 初始化边界条件,即当一个字符串为空时,转换所需的操作次数为字符串的长度。
                                                          • 动态规划通过两层循环计算每个子问题的最优解。
                                                          • 转移方程中,如果当前字符相等,则取左上方的值;如果当前字符不相等,则取左方、上方和左上方的最小值加1。
                                                          • 最后返回将 word1 的前 m 个字符转换为 word2 的前 n 个字符所需的最少操作次数,即 dp[m][n]。

                                                            8.打家劫舍

                                                            1. 打家劫舍问题定义

                                                            打家劫舍问题是一个典型的动态规划问题,目标是在一排房屋中选择一些房屋进行偷窃,但不能偷相邻的房屋,求能够获得的最大金额。

                                                            2. 动态规划方程

                                                            动态规划的转移方程为 dp[i] = max(dp[i-1], dp[i-2] + nums[i]),其中 dp[i] 表示前 i 个房屋能够获得的最大金额,nums[i] 表示第 i 个房屋中的金额。

                                                            // 定义一个函数,接受一个包含每个房屋金额的数组作为参数
                                                            function rob(nums) { const n = nums.length; // 房屋的数量
                                                                if (n === 0) { return 0; // 没有房屋,则返回0
                                                                } else if (n === 1) { return nums[0]; // 只有一个房屋,则返回该房屋的金额
                                                                }
                                                                // 初始化一个数组dp,表示前i个房屋能够获得的最大金额
                                                                const dp = new Array(n);
                                                                // 前两个房屋的最大金额为较大的那个
                                                                dp[0] = nums[0];
                                                                dp[1] = Math.max(nums[0], nums[1]);
                                                                // 动态规划,计算每个子问题的最优解
                                                                for (let i = 2; i < n; i++) { // 在偷第i个房屋和不偷第i个房屋之间取较大值
                                                                    dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);
                                                                }
                                                                // 返回前n个房屋能够获得的最大金额
                                                                return dp[n - 1];
                                                            }
                                                            // 测试例子
                                                            const nums = [2, 7, 9, 3, 1];
                                                            const result = rob(nums);
                                                            console.log(`在给定房屋中能够获得的最大金额为:${result}`);
                                                            //在给定房屋中能够获得的最大金额为:12
                                                            
                                                            • rob 函数接受一个包含每个房屋金额的数组 nums 作为参数。
                                                            • 根据房屋的数量 n 分情况处理:如果没有房屋,则返回0;如果只有一个房屋,则返回该房屋的金额。
                                                            • 初始化一个数组 dp,表示前 i 个房屋能够获得的最大金额。
                                                            • 前两个房屋的最大金额为较大的那个,即 dp[0] = nums[0] 和 dp[1] = Math.max(nums[0], nums[1])。
                                                            • 动态规划通过循环计算每个子问题的最优解,转移方程为 dp[i] = max(dp[i-1], dp[i-2] + nums[i]),表示在偷第 i 个房屋和不偷第 i 个房屋之间取较大值。
                                                            • 最后返回前 n 个房屋能够获得的最大金额,即 dp[n - 1]。

                                                              9.最大正方形

                                                              1. 最大正方形定义

                                                              最大正方形是指在一个二维矩阵中,由’1’组成的最大正方形的边长。

                                                              2. 动态规划方程

                                                              动态规划的转移方程为:

                                                              • 如果 matrix[i][j] 等于 1,则 dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1;
                                                              • 否则,dp[i][j] = 0,其中 dp[i][j] 表示以 matrix[i][j] 为右下角的最大正方形的边长。
                                                                // 定义一个函数,接受一个包含0和1的二维矩阵作为参数
                                                                function maximalSquare(matrix) { const m = matrix.length; // 矩阵的行数
                                                                    const n = matrix[0].length; // 矩阵的列数
                                                                    // 初始化一个二维数组dp,表示以matrix[i][j]为右下角的最大正方形的边长
                                                                    const dp = new Array(m).fill(0).map(() => new Array(n).fill(0));
                                                                    // 初始化结果,表示最大正方形的边长
                                                                    let maxSquare = 0;
                                                                    // 动态规划,计算每个子问题的最优解
                                                                    for (let i = 0; i < m; i++) { for (let j = 0; j < n; j++) { // 如果当前元素为1,则更新dp[i][j]为左、上、左上三个方向的最小值加1
                                                                            if (matrix[i][j] === '1') { dp[i][j] = Math.min(
                                                                                    i > 0 ? dp[i - 1][j] : 0,
                                                                                    j > 0 ? dp[i][j - 1] : 0,
                                                                                    i > 0 && j > 0 ? dp[i - 1][j - 1] : 0
                                                                                ) + 1;
                                                                                // 更新最大正方形的边长
                                                                                maxSquare = Math.max(maxSquare, dp[i][j]);
                                                                            }
                                                                        }
                                                                    }
                                                                    // 返回最大正方形的边长的平方
                                                                    return maxSquare * maxSquare;
                                                                }
                                                                // 测试例子
                                                                const matrix = [
                                                                    ['1', '0', '1', '0', '0'],
                                                                    ['1', '0', '1', '1', '1'],
                                                                    ['1', '1', '1', '1', '1'],
                                                                    ['1', '0', '0', '1', '0']
                                                                ];
                                                                const result = maximalSquare(matrix);
                                                                console.log(`最大正方形的面积为:${result}`);   
                                                                //最大正方形的面积为:4
                                                                
                                                                • maximalSquare 函数接受一个包含0和1的二维矩阵 matrix 作为参数。
                                                                • 初始化一个二维数组 dp,用于存储以 matrix[i][j] 为右下角的最大正方形的边长。
                                                                • 初始化结果 maxSquare,表示最大正方形的边长。
                                                                • 动态规划通过两层循环计算每个子问题的最优解。
                                                                • 转移方程中,如果当前元素为1,则更新 dp[i][j] 为左、上、左上三个方向的最小值加1;否则, dp[i][j] 为0。
                                                                • 在动态规划过程中,不断更新最大正方形的边长。
                                                                • 最后返回最大正方形的边长的平方,即 maxSquare * maxSquare。