算法题:动态规划

动态规划(Dynamic Programming,DP)是运筹学的一个分支,是求解决策过程最优化的过程。

动态规划的核心思想:下一个状态可以由上一个状态推导而来,因此可以利用数组保存之前计算的结果,避免重复计算

我们来看一个经典案例:斐波那契数列

function fibonacci(n) {
    if(n == 0 || n == 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2)
}

上面的代码看似简单,但其实存在一个严重问题。通过下图我们可以看出,在递归过程中,很多节点被重复计算了,这些无疑导致了性能损失,而且随着问题规模的变大,重复计算的节点呈几何级数增长。

算法题:动态规划_第1张图片

那么怎么样优化这个流程呢?由斐波那契数列的特性可以看出,第 n 个节点的值可以由第 n-1 和第 n-2 个节点推导出来,符合动态规划的逻辑,因此我们可以用一个数组保存之前的状态,避免重复计算。

function fibonacci(n) {
    if (n == 0 || n == 1) return n; // 边界值处理
    let dp = new Array(n);
    dp[0] = 0; // 初始条件
    dp[1] = 1;
    for (let i=2; i

上面的代码中,我们用了一个长度为 n 数组保存之前的状态,时间复杂度降低到了 O(n) ,极大提高了效率。从上面的代码中,我们可以总结出动态规划的两个核心组成部分:初始条件和边界情况 以及 状态转移方程。所谓状态转移方程,也就是由上一个状态推导到下一个状态的递推公式。在上面的例子中,状态专转移方程可以抽象为:

f(i) = f(i-1) + f(i-2)

一般来说,求第 n 个元素,需要开一个长度为 n 的数组,空间复杂度为 O(n) 。但是在斐波那契数列的例子中,我们只关心当前状态的前两个状态就行,不需要把之前所有的状态都给保存下来。因此,上面的代码还可以再优化,只创建一个长度为 2 的数组就满足需要了,空间复杂度降为常数阶。

function fibonacci(n) {
    if (n == 0 || n == 1) return n;
    let dp = [0, 1];
    for (let i=2; i

剑指 Offer 42 连续子数组的最大和

class Solution {
    public int maxSubArray(int[] nums) {
        int len = nums.length;
        int[] dp = new int[len];
        dp[0] = nums[0];
        int max = nums[0];

        for (int i=1; i

leetcode 64 最小路径和

leetcode 300 最长递增子序列

参考:

肝了好多天-动态规划十连-超细腻解析|刷题打卡

动态规划 - 小彭的博客

你可能感兴趣的:(前端算法-数据结构)