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

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

1.斐波那契数列

1. 斐波那契数列定义

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

2. 动态规划方程

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

js 复制代码
// 定义一个函数,接受斐波那契数列的序号作为参数
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 级楼梯的方法数。

js 复制代码
// 定义一个函数,接受楼梯的级数作为参数
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 个物品的价值。

js 复制代码
// 定义一个函数,接受物品的重量数组和价值数组以及背包的容量作为参数
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 个元素结尾的最长递增子序列的长度,j0i-1 的索引,且 nums[i] > nums[j]

js 复制代码
// 定义一个函数,接受数组作为参数
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 个元素结尾的最大子数组和。

js 复制代码
// 定义一个函数,接受数组作为参数
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 个字符的最长公共子序列的长度。
js 复制代码
// 定义一个函数,接受两个字符串作为参数
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 函数接受两个字符串 str1str2 作为参数。
  • 初始化一个二维数组 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 个字符所需的最少操作次数。
js 复制代码
// 定义一个函数,接受两个单词作为参数
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 函数接受两个单词 word1word2 作为参数。
  • 初始化一个二维数组 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 个房屋中的金额。

js 复制代码
// 定义一个函数,接受一个包含每个房屋金额的数组作为参数
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] 为右下角的最大正方形的边长。
js 复制代码
// 定义一个函数,接受一个包含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
相关推荐
妙哉73613 分钟前
零基础学安全--HTML
前端·安全·html
武昌库里写JAVA16 分钟前
SpringCloud+SpringCloudAlibaba学习笔记
java·开发语言·算法·spring·log4j
咔叽布吉20 分钟前
【前端学习笔记】AJAX、axios、fetch、跨域
前端·笔记·学习
小咖拉眯28 分钟前
第十六届蓝桥杯模拟赛第二期题解—Java
java·数据结构·算法·蓝桥杯·图搜索算法
Sunyanhui130 分钟前
力扣 最长回文字串-5
算法·leetcode·职场和发展
csdn_aspnet34 分钟前
C# 程序来计算三角形的面积(Program to find area of a triangle)
算法·c#
xiangxiang-34 分钟前
目标检测,图像分割,超分辨率重建
算法·机器学习·支持向量机
一直学习永不止步35 分钟前
LeetCode题练习与总结:数组中两个数的最大异或值--421
java·算法·leetcode·字典树·数组·位运算·哈希表
GISer_Jing41 分钟前
Vue3常见Composition API详解(适用Vue2学习进入Vue3学习)
前端·javascript·vue.js
机器学习之心43 分钟前
异常检测 | 高斯分布拟合算法异常数据检测(Matlab)
算法·数学建模·matlab·异常数据检测