动态规划终极无敌解析1

千万不要以名字理解这个算法,一点都不动态。
其实主要思想就是假设我已经知道了f(n-1),f(n-2)。。。。怎么去求f(n)。
换句话说是那些f()可以到达 f(n)。f(n)=f(能到达1)+f(能到达2)…

1、 能用动态规划解决的问题

如果一个问题满足以下两点,那么它就能用动态规划解决。

1、问题的答案依赖于问题的规模,也就是问题的所有答案构成了一个数列

例如:一个人有两条腿,2个人有四条腿,n个人有几条腿?答案是2n,这里2n是问题的答案,n是问题的规模,显然问题的答案是依赖问题的规模的。答案是因变量,问题规模是自变量。因此,问题在所有规模下的答案可以构成一个数列(f(1),f(2)…f(n))。

2、大规模问题的答案可以由小规模问题的答案递推得到。

刚才的例子,腿数f(n)可以由f(n-1)求得:f(n)=f(n-1)+2
这么一看有点像递归了。

3、适合用动态规划解决的问题

较为简单的问题例如上边的例子可以用f(n)=2n的表达式直接表示,那么就不必多此一举了。但是在许多场景,f(n)不是这么容易得到,这时候动态规划的魅力就出来了。

动态规划的用法

当要应用动态规划来解决问题的时候,就是想办法完成下面三个关键目标。

1、建立状态方程

这应该是最难的一步,这一步没有太多的规律可说,只需要把握住一个思维:假装我们已经知道了f(1)~f(n-1)的值,然后让他们想办法求得f(n)。在第一个例子中转移方程即为:f(n)=f(n-1)+2

其实就是再求通项公式。。。。(高中数学题,)

缓存并复用以往的结果

我认为这是区分和递归不同的点。
他可以存储以往结果避免了重复计算,大大节省了空间复杂度。
例如上边的例子,如果我们要求f(100),但是刚才求解过f(99)。如果不将其缓存起来,那么求f(100)还需要大量的计算。如果刚刚缓存过,只需要一次运算就可以得到结果。

其实就是搞一个数组变量记录中间的结果。

按顺序从小往大算

题目:

1、斐波那契数列(简单):

斐波那契数列:1,1,2,3,5,8…
当前值为前两个的值。那么第n个值是多少?

1、简单递归

function fib(n) {
    if (n<2){
        return n
    }else {
        console.log(a++)
        return fib(n-1)+fib(n-2)
    }
}

这个算法现在的复杂度为O(2**n)。
因为其中有大量的重复计算,所以时间复杂度大大提高。

1

2. 动态规划

function dfib(n) {
    let result = [];
    for (let j = 0; j < n.length; j++) {
        result.push(1);
    }    ;
    for (let i = 0; i < n.length; i++) {
        if (i < 2) result[i]=1;
        else {
            //使用状态方程,同时复用以往的结果
            result[i]=result[i-1]+result[i-2]
        }
        return result[-1]
    }
}

2、不同路径(困难)

1

一个机器人位于一个网格的左上角,机器人智能向下或者向右移动一步。那么有多少种方法可以到达右下角?

解这道题,先想一下怎么完成三个子目标(1、建立方程。2、缓存数据。3、由小到大求解)。

1、建立状态转移方程
这是最难的一步(想想一下我们已经知道了f(n-1),f(n-2)。。。怎么求f(n),最难的就是想出怎么定义f(n))。如图所示,第i行和第j列的格子路径数,是等于它左边格子和上面格子的路径数之和:
1

1
2、缓存以往的结果
与以往的一位数组不同,这里需要定义一个2维数组,这里的中间结果可以构成一个二维数列。(我认为一般都是f()的参数数量)。

3、按顺序从小到大算。
这次又两个维度,所以需要两次循环,分别逐行逐列的让规模变的更小。(其实因为存储数列是两维的,基本上是按照填充数列的顺序来)

具体代码:

function dpath(m, n) {
    let result = [];

    for (let i = 0; i < n; i++) {
        result.push(mn);
        for (let j = 0; j < n; j++) {
            mn=[];
            mn.push(1);
        }
    };
    result.shift();
    for (let i; i < m; i++) {
        for (let j; j < n; j++) {
            result[i][j]=result[i-1][j]+result[i][j-1]
        }
    }
    return result[-1][-1]
}

3、爬楼梯

题目描述:

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

注意:给定 n 是一个正整数。

首先还是三部:

1、动态方程(已知f(n-1),f(n-2)…求f(n))
到达f(n)只能是f(n-1),和f(n-2)
f(n)=f(n-1)+f(n-2)
2、缓存结果(一位数组,f(n)的参数是一个)
3、由小到大求解

function dpa(n) {
    for (let i = 0; i < n.length ; i++) {
        let b = undefined;
        result.push(b);
    }
    for (let i=0;i<n.length;i++){
        if (i<3) {
            result[i]=i
        }else {
            result[i]=result[i-1]+result[i-2]
        }
    }
    return result

}

4、超经典的背包问题

题目:有一个容量为 V 的背包,和一些物品。这些物品分别有两个属性,体积 w 和价值 v,每种物品只有一个。要求用这个背包装下价值尽可能多的物品,求该最大价值,背包可以不被装满。
因此定为背包容量为j时,求前i个物品所能达到最大价值。最终状态为f(i,j)

首先还是三部曲:
1、动态方程。
这里将动态方程分为三步:

步骤1-找子问题:

子问题必然是和物品有关的,对于每一个物品,有两种结果:能装下或者不能装下。第一,包的容量比物品体积小,装不下,这时的最大价值和前i-1个物品的最大价值是一样的。第二,还有足够的容量装下该物品,但是装了不一定大于当前相同体积的最优价值,所以要进行比较。由上述分析,子问题中物品数和背包容量都应当作为变量。因此子问题确定为背包容量为j时,求前i个物品所能达到最大价值。

步骤2-确定状态:

由上述分析,“状态”对应的“值”即为背包容量为j时,求前i个物品所能达到最大价值,设为dp[i][j]。初始时,dp[0]j为0,没有物品也就没有价值。

步骤3-确定状态转移方程:

由上述分析,第i个物品的体积为w,价值为v,则状态转移方程为

**注释:**假如我们想要求f(3,4),3的重量是2,那么放入3之后为f(2,1),将3的价值和f(2,1)相加即为f(3,4)加入3的状态,然后和f(3-1,4)想比较,取值大的即为f(3,4)的值。

j<w,dp[i][j] = dp[i-1][j] 
 //背包装不下该物品,最大价值不变
j>=w, dp[i][j] = max{ dp[i-1][j-list[i].w] + v, dp[i-1][j] } 
//和不放入该物品时同样达到该体积的最大价值比较

2、缓存数据:定义一个二维数组 i j

3、重用

let num = 8;
let array1 = [2, 2, 6, 5, 4]
let array2 = [6, 3, 5, 4, 6]

function bag(num, obj1, obj2) {
    let result = [];
    for (let i = 0; i < num + 1; i++) {
        let b = [];
        result.push(b);
        for (let j = 0; j < obj1.length; j++) {
            b.push(0)
        }
    }    ;
    result.shift()
    for (let i = 1; i < obj1.length + 1; i++) {
        for (let j = 0; j < n; j++) {
            if (i < array1[j]) {
                result[i][j] = result[i - 1][j]
            } else {
                result[i][j] = Math.max(result[i - 1][i - array2[j]], result[i - 1][j])
            }
        }
    }
    return result
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值