js实现算法之动态规划

栏目: 编程工具 · 发布时间: 5年前

内容简介:本文主要总结了动态规划的几种经典题型,分别为经典01背包、最少硬币、最大正方形、最大加号标志。背包容量capacity=5,有三个物品(value, weight),分别是(3,2),(4,3),(5,4)求出其搭配组合,使得背包内总价最大,且最大价值为多少?首先理清思路列出表格:

本文主要总结了动态规划的几种经典题型,分别为经典01背包、最少硬币、最大正方形、最大加号标志。

一、经典01背包

1. 题目

背包容量capacity=5,有三个物品(value, weight),分别是(3,2),(4,3),(5,4)求出其搭配组合,使得背包内总价最大,且最大价值为多少?

2.思路

首先理清思路列出表格:

如果背包总容量为0,那么很显然地,任何物品都无法装进背包,那么背包内总价值必然是0。所以第一步先填满 j=0 的情况。

接下来将从上到下,从左往右地填写这个表格。分析第i行时,它的物品组合仅能是小于等于i的情况。所以现在把注意力定位到 i =0, j = 1 的空格上。

i=0 j=1 : 背包总容量为1,但是物品0 的重量为 2,无法装下去,所以这一格应该填 0。

i=0 j=2 : 背包总容量为2,刚好可以装下物品0 ,由于物品0 的价值为3,因此这一格填 3。

后面同理。

i=1 j=1 : 背包总容量为1,但是物品0 的重量为 2,物品1重量为3,背包无法装下任何物品,所以填 0。

val weight j=0 j=1 j=2 j=3 j=4 j=5
i=0 3 2 0 0 3 3 3 3
i=1 4 3 0 0 3 4 4 7
i=2 5 4 0 0 3 4 5 7

伪代码:

if(j>w[i]){
    T[i][j] = T[i-1][j]
}else{
    T[i][j] = max(T[i-1][j-w[i]]+val[i], T[i-1][j])
}

3.代码

function backpack(w,val,capacity,n){
    var T = []

    for(let i = 0;i < n;i++){
        T[i] = [];
        for(let j=0;j <= capacity;j++){
            if(j === 0){ //容量为0
                T[i][j] = 0;
                continue;
            }	
            if(j < w[i]){ //容量小于物品重量,本行hold不住
                if(i === 0){
                    T[i][j] = 0; // i = 0时,不存在i-1,所以T[i][j]取0

                }else{
                    T[i][j] = T[i-1][j]; //容量小于物品重量,参照上一行
                }
                continue;
            }
            if(i === 0){
                T[i][j] = val[i]; //第0行,不存在 i-1, 最多只能放这一行的那一个物品
            }else{
                T[i][j] = Math.max(val[i] + T[i-1][j-w[i]],T[i-1][j]);
            }
        }
    }
    findValue(w,val,capacity,n,T);
    return T;
}
//找到需要的物品
function findValue(w,val,capacity,n,T){
    var i = n-1, j = capacity;
    while ( i > 0 && j > 0 ){
        if(T[i][j] != T[i-1][j]){
            console.log('选择物品'+i+',重量:'+ w[i] +',价值:' + values[i]);
            j = j- w[i];
            i--;
        }else{
            i--;  //如果相等,那么就到 i-1 行
        }
    }
    if(i == 0 ){
        if(T[i][j] != 0){ //那么第一行的物品也可以取
            console.log('选择物品'+i+',重量:'+ w[i] +',价值:' + values[i]);
        }
    }
}
var values = [3,4,5],
weights = [2,3,4],
capacity = 5,
n = values.length;

console.log(backpack(weights,values,capacity,n));

二、最少硬币

1. 题目

4种硬币 1分、2分、5分、6分

总共需要11分,求最少的硬币数以及组合

2.思路

首先理清思路列出表格:

与经典背包相同,填写第i行表示只能用i和比i小的硬币。硬币数组coins[],需要的钱数j。

当我们只能使用面额为1分的硬币时,根据上面的规则,那么很显然,总额为几分,就需要几个硬币。

当我们有1分和2分两种面额时,那么组合方式就相对多了点。

i=1 j = 1:总额为1时,只能使用1分的面额。即填1。

i=1 j = 2:总额为2时,可以使用2个1分的,也可以使用1个2分的。

因为我们要求最少硬币,所以使用1个2分的。表格里填1。

以此类推:

j=0 j=1 j=2 j=3 j=4 j=5 j=6 j=7 j=8 j=9 j=10 j=11
i=0 1分 0 1 2 3 4 5 6 7 8 9 10 11
i=1 2分 0 1 1 2 2 3 3 4 4 5 5 6
i=2 5分 0 1 1 2 2 1 2 2 3 3 2 3
i=3 6分 0 1 1 2 2 1 1 2 2 3 3 2

伪代码:

if(j<coins[i]){
    T[i][j] = T[i-1][j]
}else{
    T[i][j] = min(T[i-1][j], T[i][j-coins[i]]+1)
}

3.代码

/**
* @param {number[]} coins
* @param {number} amount
* @return {number}
*/
var coinChange = function(coins, amount) {
    coins.sort((a,b)=>(a-b));
    var T = [];
    for(let i=0; i<coins.length; i++){
        T[i] = [];
        for(let j=0; j<=amount; j++){
            T[i][j] = 0;
        }
    }
    for(let i=0; i<T.length; i++){
        for(let j=0; j<T[i].length; j++){
            if(j==0){
                T[i][j] = 0;
                continue;
            }
            if(i==0){
                if(Number.isInteger(j/coins[i])){
                    T[i][j] = j/coins[i];
                }else{
                    T[i][j] = Infinity;
                }
            }else{
                if(j<coins[i]){
                    T[i][j] = T[i-1][j];
                }else{
                    T[i][j] = Math.min(T[i-1][j], T[i][j-coins[i]]+1);
                }
            }
        }
    }
    return T[T.length-1][T[0].length-1]===Infinity?-1:T[T.length-1][T[0].length-1];
};

三、最大正方形

1. 题目

在一个由 0 和 1 组成的二维矩阵内,找到只包含 1 的最大正方形,并返回其面积。

示例:

输入:

1 0 1 0 0

1 0 1 1 1

1 1 1 1 1

1 0 0 1 0

输出: 4

2.思路

首先用例中不一定会给正方形,也可能是长方形,所以必须分别求长和宽。

其次如果此点的值是0,则直接将结果设为0;如果此点的值为1,则它等于它左方、上方、左上方三者的最小值+1。

最后是需要一个变量max,在遍历的过程中不断修改自身获取dp中的最大值。

js实现算法之动态规划

3.代码

/**
 * @param {character[][]} matrix
 * @return {number}
 */
var maximalSquare = function(matrix) {
    let len1 = matrix.length, len2;
    var dp = new Array(len1);//设置长度
    let max = 0;//记录矩阵中最大值
    for(let i=0; i<len1; i++){
        len2 = matrix[i].length;
        dp[i] = new Array(len2);
    }
    for(let i=0; i<len1; i++){
        for(let j=0; j<len2; j++){
            if(i==0 || j==0){
                dp[i][j] = matrix[i][j] == '1'?1:0;
            }else{
                dp[i][j] = matrix[i][j] == '1'? (Math.min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1])+1):0;
            }
            max = Math.max(max, dp[i][j]);
        }
    }
    return max*max;
};

四、最大加号标志

1. 题目

在一个大小在 (0, 0) 到 (N-1, N-1) 的2D网格 grid 中,除了在 mines 中给出的单元为 0,其他每个单元都是 1。网格中包含 1 的最大的轴对齐加号标志是多少阶?返回加号标志的阶数。如果未找到加号标志,则返回 0。

一个 k” 阶由 1 组成的“轴对称”加号标志具有中心网格 grid[x][y] = 1 ,以及4个从中心向上、向下、向左、向右延伸,长度为 k-1,由 1 组成的臂。下面给出 k” 阶“轴对称”加号标志的示例。注意,只有加号标志的所有网格要求为 1,别的网格可能为 0 也可能为 1。

k 阶轴对称加号标志示例:

阶 1:

000

010

000

阶 2:

00000

00100

01110

00100

00000

阶 3:

0000000

0001000

0001000

0111110

0001000

0001000

0000000

示例 1:

输入: N = 5, mines = [[4, 2]]

输出: 2

解释:

11111

11111

11111

11111

11011

在上面的网格中,最大加号标志的阶只能是2。一个标志已在图中标出。

示例 2:

输入: N = 2, mines = []

输出: 1

解释:

11

11

没有 2 阶加号标志,有 1 阶加号标志。

示例 3:

输入: N = 1, mines = [[0, 0]]

输出: 0

解释:

0

没有加号标志,返回 0 。

2.思路

首先要给所有位置设置最大值即边长N。

let dp = [...Array(N)].map(() => Array(N).fill(N));

循环遍历所有行,从四个方向更新dp。

for(let i=0; i<N; i++){
        for(let l=0, left=0; l<N; l++){
            dp[i][l] = Math.min(dp[i][l], left = (dp[i][l] == 0 ? 0: left+1));
        }
        for(let r=N-1, right=0; r>=0; r--){
            dp[i][r] = Math.min(dp[i][r], right = (dp[i][r] == 0 ? 0: right+1));
        }
        for(let u=0, up=0; u<N; u++){
            dp[u][i] = Math.min(dp[u][i], up = (dp[u][i] == 0 ? 0: up+1));
        }
        for(let d=N-1, down=0; d>=0; d--){
            dp[d][i] = Math.min(dp[d][i], down = (dp[d][i] == 0 ? 0: down+1));
        }
    }

left right up down注意每遍历一行都要更新为0。

3.代码

/**
 * @param {number} N
 * @param {number[][]} mines
 * @return {number}
 */
var orderOfLargestPlusSign = function(N, mines) {
    let dp = [...Array(N)].map(() => Array(N).fill(N));
    for(let i=0; i<mines.length; i++){
        dp[mines[i][0]][mines[i][1]] = 0;
    }
    let left, right, up, down;
    for(let i=0; i<N; i++){
        for(let l=0, left=0; l<N; l++){
            dp[i][l] = Math.min(dp[i][l], left = (dp[i][l] == 0 ? 0: left+1));
        }
        for(let r=N-1, right=0; r>=0; r--){
            dp[i][r] = Math.min(dp[i][r], right = (dp[i][r] == 0 ? 0: right+1));
        }
        for(let u=0, up=0; u<N; u++){
            dp[u][i] = Math.min(dp[u][i], up = (dp[u][i] == 0 ? 0: up+1));
        }
        for(let d=N-1, down=0; d>=0; d--){
            dp[d][i] = Math.min(dp[d][i], down = (dp[d][i] == 0 ? 0: down+1));
        }
    }
    var max = 0;
    for(let i=0; i<N; i++){
        for(let j=0; j<N; j++){
            max = Math.max(max, dp[i][j]);
        }
    }
    return max;
};

总结:

本文主要对动态规划几种经典题做了简单介绍,希望对大家对算法方面的学习有所帮助,总结不到位的地方还请大家批评指正。

友情参考链接:

https://juejin.im/post/5affed3951882567161ad511

https://juejin.im/post/5b0a8e0f51882538b2592963

https://leetcode-cn.com/problemset/all/

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

测试驱动开发的艺术

测试驱动开发的艺术

Lasse Koskela / 李贝 / 人民邮电出版社 / 20101023 / 59.00元

在传统的软件开发中,开发人员对于代码是否正确心中无底,一切依赖于后期的测试环节。极限编程反其道而行之,主张采用测试驱动开发(TDD)的方法,即通过测试定义所要开发的功能的接口,然后实现功能的开发过程。TDD通过不断地测试推动代码的开发,既简化了代码,又保证了软件质量。 本书采用“手把手”的教学方式,通过大量实例来解释TDD,还专门用几章的篇幅来讲解如何为难于测试的技术编写单元测试。全书内容循......一起来看看 《测试驱动开发的艺术》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

MD5 加密
MD5 加密

MD5 加密工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具