动态规划(js版)

2023-11-01

1. 动态规划算法介绍

理解动态规划 ~ 知乎好文
在这里插入图片描述
LeetCode简单的动态规划题:

LeetCode较难的动态规划题:

总结:

动态规划与其说是一个算法,不如说是一种方法论。
该方法论主要致力于将“合适”的问题拆分成三个子目标一一击破:

  • 1.建立状态转移方程
  • 2.缓存并复用以往结果
  • 3.按顺序从小往大算

2. “01背包”问题

概念:有N件物品和一个最多能装重量为W 的背包。第i件物品的重量是weight[i],价值是value[i]。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

本案例视频链接 ~ 尚硅谷
在这里插入图片描述
分析:
在这里插入图片描述
在这里插入图片描述
图解分析:
在这里插入图片描述
案例分析:

1. 假如现在只有  吉他(G)这时不管背包容量多大,只能放一个吉他1500(G) 
2. 假如有吉他和音响, 验证公式:v[1][1] =1500
	(1). i = 1, j = 1 
	(2). w[i] = w[1] = 1 j = 1   
	v[i][j]=max{v[i-1][j], v[i]+v[i-1][j-w[i]]} : 
	v[1][1] = max {v[0][1], v[1] + v[0][1-1]} = max{0, 1500 + 0} = 1500
3. 假如有吉他/音响/电脑, 验证公式:v[3][4] 
	(1). i = 3;j = 4
	(2). w[i] = w[3] =3  j = 4
	j = 4 >= w[i] = 3 => 4 >= 3
	v[3][4] = max {v[2][4], v[3] + v[2][1]} = max{3000, 2000+1500} = 2000+1500

归纳:

从表格的右下角开始回溯,如果发现前n个物品的最佳组合的价值和前n-1个物品最佳组合的价值一样,说明第n个物品没有被装入;否则,第n个物品被装入。

问题:背包容量为4时,能装入物品的最大价值是多少?

代码:第一种写法

const w = [1, 4, 3]; //物品重量
const value = [1500, 3000, 2000]; //物品的价值 
const m = 4; //背包容量
const n = 3; //物品的个数

// 二维数组:v[i][j] 表示在前i个物品中能够装入容量为j的背包中的最大价值
let v = new Array(n + 1).fill(0).map(() => new Array(m + 1).fill(0));

// i控制行,j控制列
for (let i = 1; i <= n; i++) { // 先遍历物品
    for (let j = 1; j <= m; j++) { // 后遍历背包容量
    	// 这里使用w[i - 1]是避免跳过第一个物品,同理else中的语句也是一样的
        if (w[i - 1] > j) {
            v[i][j] = v[i - 1][j];
        } else {
            v[i][j] = Math.max(v[i - 1][j], value[i - 1] + v[i - 1][j - w[i - 1]]);
        }
    }
}

// for (let j = 1; j <= m; j++) { //先遍历背包容量
//     for (let i = 1; i <= n; i++) { //后遍历物品
//         if (w[i - 1] > j) {
//             v[i][j] = v[i - 1][j];
//         } else {
//             v[i][j] = Math.max(v[i - 1][j], value[i - 1] + v[i - 1][j - w[i - 1]]);
//         }
//     }
// }
console.log(v[n][m]); //3500
// 二维数组v的结果如下:
// [
//     [0, 0, 0, 0, 0],
//     [0, 1500, 1500, 1500, 1500],
//     [0, 1500, 1500, 1500, 3000],
//     [0, 1500, 1500, 2000, 3500]
// ]

// 复杂度分析
// 时间复杂度:O(m*n)
// 空间复杂度:O(m*n)

问题:为何既可以先遍历物品,也可以先遍历背包的容量呢?(为何for循环遍历次序可以不同?)

答:由递推公式可以看出,v[i][j] 是靠 v[i-1][j] 和 v[i - 1][j - w[i-1]] 推导出来的,v[i-1][j] 和 v[i - 1][j - w[i-1]] 都位于 v[i][j] 的左上角方向(包括正上方向),即先打印行或者列,都不影响 v[i][j] 公式的推导。

代码:第二种写法【原理与第一种一样,只不过初始化的过程略有调整】

const w = [1, 4, 3]; //物品重量
const value = [1500, 3000, 2000]; //物品的价值 
const m = 4; //背包容量
const n = 3; //物品的个数
let v = new Array(n).fill(0).map(() => new Array(m + 1).fill(0));

// 初始化
for (let j = w[0]; j <= m; j++) {
    v[0][j] = value[0];
}

for (let i = 1; i < n; i++) { //先遍历物品
    for (let j = 0; j <= m; j++) { //后遍历背包容量
        // 初始化时,第一个物品已经安排上了(第一行打印完成),故不需要w[i-1]了
        if (w[i] > j) {
            v[i][j] = v[i - 1][j];
        } else {
            v[i][j] = Math.max(v[i - 1][j], value[i] + v[i - 1][j - w[i]]);
        }
    }
}

// for (let j = 0; j <= m; j++) { //先遍历背包容量
//     for (let i = 1; i < n; i++) { //后遍历物品
//         if (w[i] > j) {
//             v[i][j] = v[i - 1][j];
//         } else {
//             v[i][j] = Math.max(v[i - 1][j], value[i] + v[i - 1][j - w[i]]);
//         }
//     }
// }
console.log(v);
// 二维数组v的结果如下:
// [
//     [0, 1500, 1500, 1500, 1500],
//     [0, 1500, 1500, 1500, 3000],
//     [0, 1500, 1500, 2000, 3500]
// ]

优化代码:将空间复杂度降为O(n)

二维数组变一维数组: 在一维dp数组中,dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j]。

const w = [1, 3, 4];
const value = [15, 20, 30];
const n = 4; //背包最大容量
const m = w.length; //物品的个数
let dp = new Array(n + 1).fill(0);

for (var i = 1; i <= m; i++) {
    for (var j = n; j >= w[i - 1]; j--) {
        dp[j] = Math.max(dp[j], dp[j - w[i - 1]] + value[i - 1]);
        // dp[j]: 未放当前物品---> 因为是倒序的,初始化的时候空
        // dp[j - w[i - 1]:剩余背包容量所能装入物品的最大价值
    	// value[i - 1]当前物品的价值
    }
}
console.log(dp);//[ 0, 15, 15, 20, 35 ]

注意要点:

  1. 内层循环倒序遍历(且注意判断条件:j >= w[i - 1]

原因:倒叙遍历是为了保证物品 i 只被放入一次!一旦正序遍历了,那么物品就会被重复加入多次!

  1. 为何能变为一维数组

原因:实际上是把dp[i - 1]那一层拷贝到dp[i]上。

LeetCode中 “01背包” 题型汇总:

3. “完全背包”问题

概念:有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],价值是value[i]。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。

完全背包和01背包问题唯一不同的地方就是,每种物品有无限件。
代码:

const w = [1, 3, 4];
const value = [15, 20, 30];
const n = 4; //背包最大容量
const m = w.length; //物品的个数
let dp = new Array(m + 1).fill(0).map(v => new Array(n + 1).fill(0));

for (var i = 1; i <= m; i++) {
    for (var j = 1; j <= n; j++) {
        for (var k = 0; k <= j / w[i - 1]; k++) {
            if (w[i - 1] > j) {
                dp[i][j] = dp[i - 1][j];
            } else {
                dp[i][j] = Math.max(dp[i - 1][j], k * value[i - 1] + dp[i][j - k * w[i - 1]]);
            }
        }

    }
}
console.log(dp);
// [
//     [0, 0, 0, 0, 0],
//     [0, 15, 30, 45, 60],
//     [0, 15, 30, 45, 60],
//     [0, 15, 30, 45, 60]
// ]

优化代码:将空间复杂度降为O(n)。二维数组变一维数组

视频链接:完全背包问题

  • 第一种写法:
const w = [1, 3, 4];
const value = [15, 20, 30];
const n = 4; //背包最大容量
const m = w.length; //物品的个数
let dp = new Array(n + 1).fill(0);

for (var i = 1; i <= m; i++) {
    for (var j = n; j >= w[i - 1]; j--) {
        for (var k = 0; k <= j / w[i - 1]; k++) {
            dp[j] = Math.max(dp[j], dp[j - k * w[i - 1]] + k * value[i - 1]);
        }
    }
}
console.log(dp);
//[0, 15, 30, 45, 60]
  • 第二种写法(推荐
const w = [1, 3, 4];
const value = [15, 20, 30];
const n = 4; //背包最大容量
const m = w.length; //物品的个数
let dp = new Array(n + 1).fill(0);

for (var i = 1; i <= m; i++) {
    for (var j = w[i - 1]; j <= n; j++) {
        dp[j] = Math.max(dp[j], dp[j - w[i - 1]] + value[i - 1]);
    }
}
console.log(dp);
//[0, 15, 30, 45, 60]

第二种写法注意要点:

  • 内层循环变为正向遍历
  • 递推方程式与01完全背包一致

LeetCode中 “完全背包” 题型汇总:

总结:

求装满背包有几种方法,递归公式都是一样的,递推公式一般为:dp[j] += dp[j - nums[i]]; 但关键在于遍历顺序不同!

  • 如果求组合数就是外层for循环遍历物品,内层for遍历背包。

  • 如果求排列数就是外层for遍历背包,内层for循环遍历物品。

4. “打家劫舍”系列

5. “股票”系列【大多可用“贪心”思维】

6. “子序列”系列

以下标黄题目思路基本一致:

7. “跳跃游戏”系列【可用“贪心”思维】

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

动态规划(js版) 的相关文章

随机推荐

  • Batch和Epoch之间的区别是什么?

    写在前面 快速理解 随机梯度下降 SGD 是一种迭代学习算法 它使用训练数据集来更新模型 Batch 批量 大小是梯度下降算法的超参数 在模型的内部参数更新之前控制训练样本的数量 一个周期内一次批量训练的样本数 Epoch数是梯度下降算法的
  • python 图片与二进制之间的转换

    一 PIL格式图片转成二进制 先读取为PIL格式 再转为二进制 import io import base64 from PIL import Image def image2byte image 图片转byte image 必须是PIL格
  • java代码分层 handle_java 代码分层

    JAVA代码层次 阿里推荐 开放接口层 可直接封装 Service 方法暴露成 RPC 接口 通过 Web 封装成 http 接口 进行 网关安全控制 流量控制等 终端显示层 各个端的模板渲染并执行显示的层 当前主要是 velocity 渲
  • PyTorch torch.optim.lr_scheduler 学习率设置 调参 -- CosineAnnealingLR

    lr scheduler 学习率 学习率的参数调整是深度学习中一个非常重要的一项 Andrew NG 吴恩达 认为一般如果想调参数 第一个一般就是学习率 作者初步学习者 有错误直接提出 热烈欢迎 共同学习 感谢Andrew ng的机器学习和
  • easyui tabs 一个窗口修改完成后刷新另一个窗口

    在一个tab中添加或删除数据后 要改变主页 相当于链接的另一个tab 的内容 1 在要刷新的窗口的初始化中添加 js 刷新方法 并保存到 window top 中 window top Refresh CloudHomePage Conte
  • 二、基础平滑、面积折线图与折线堆叠、面积堆叠《手把手教你 ECharts 数据可视化详解》

    注 本系列教程需要对应 JavaScript html css 基础 否则将会导致阅读时困难 本教程将会从 ECharts 的官方示例出发 详解每一个示例实现 从中学习 ECharts ECharts 官方示例 https echarts
  • Mybatis学习(二)--getMapper接口绑定方案和多参数传值

    在Mybatis的基础使用中 如果想向一个sql语句中传递多个参数 只能将parameterType设置为某个类或者Map 不能直接传入多个参数 接口绑定方案可以实现直接传入多个参数 Mybatis的接口绑定方案与基本的使用方法不同的地方在
  • unity 射线获取坐标

    射线 碰到障碍物就会断开 鼠标点击屏幕获得一个二维坐标 通过相机的射线转换为三维世界坐标 private Vector3 worldPos 鼠标点击的点所对应的世界里面的位置 点击鼠标右键 if Input GetMouseButton 1
  • ThinkPHP文件包含漏洞分析

    出品 长白山攻防实验室 ID A Tree 0x00 声明 以下内容 来自长白山攻防实验室的A Tree作者原创 由于传播 利用此文所提供的信息而造成的任何直接或间接的后果和损失 均由使用者本人负责 长白山攻防实验室以及文章作者不承担任何责
  • Vue3集成高德地图方法

    1 注册高德开发者账号 获取key和安全密钥 2 下载依赖 可参考高德官方文档 https lbs amap com api jsapi v2 guide webcli map vue1 npm i amap amap jsapi load
  • GD32f103 8M晶振改12M , 要修改的地方

    手里的单片机是gd32f103ret6 晶振和官方库默认的8M不一致 导致串口乱码 网上找了好久全是STM32的例子 不过还是有参考意义的 以下是gd32f10x 的设置方式 1 Keil中的Target设置 PS 这一项好像会自动设置 安
  • 7、变量进阶

    7 变量进阶 理解 目标 变量的引用 可变和不可变类型 局部变量和全局变量 01 变量的引用 变量 和 数据 都是保存在 内存 中的 在 Python 中 函数 的 参数传递 以及 返回值 都是靠 引用 传递的 1 1 引用的概念 在 Py
  • [论文阅读]《how to share a secret》

    how to share a secret Adi Shamir 文章主要讲了如何将数据D分为n份 任意k份可以重组成D 任意k 1份不会泄露任何关于D的信息 这种技术能为密码系统构建鲁棒的密钥管理机制 即使灾难破坏一半信息或者安全性被破坏
  • 浅谈C++

    引子 程序运行时产生的数据都属于临时数据 程序一旦运行结束都会被释放通过文件可以将数据持久化 C 中对文件操作需要包含头文件 lt fstream gt C 提供了丰富的文件操作功能 你可以使用标准库中的fstream库来进行文件的读取 写
  • 【接口测试】POST请求提交数据的三种方式及Postman实现

    1 什么是POST请求 POST请求是HTPP协议中一种常用的请求方法 它的使用场景是向客户端向服务器提交数据 比如登录 注册 添加等场景 另一种常用的请求方法是GET 它的使用场景是向服务器获取数据 2 POST请求提交数据的常见编码格式
  • 【从零到一的Raspberry】数莓派踩坑实录(二) 内核编译配置和模块安装

    写在前面 本次作业具有挑战性 不过不管哪一环节出错了 你都要知道如何把它还原到初始状态 这样你就不是在危险地操作 而有还原的保障 因此在第0节我会介绍一种还原数莓派系统的方法 这样你就可以在内核无法运行时还原到默认系统 后面从第一章开始 带
  • AD 利用IPC封装创建向导快速创建封装

    首先在扩展更新里查看是否有IPC封装 工具里面第二个会有很多常见封装类型 选择SOP NEXT 会填写一些数据 相对应在数据手册上进行填写即可 下图左上角问的是要不要加散热焊盘 散热焊盘主要看原件是否真实需要 上图要填的值一般来说默认就可以
  • Ubuntu无法连接网络?

    文章目录 适用情况 Windows网络配置和虚拟机网络配置 Windows网络适配器配置 Ubuntu设置静态IP 图形化界面操作 指令文件操作 如果重新设置好以后 依旧不行 适用情况 如果您无法知晓 虚拟机出现是什么问题 始终就是无法连接
  • 黑盒测试的范围内容

    1 功能错误或遗漏 2 界面错误 3 数据结构或外部数内容据库访问错误 4 性能错误 5 初始化和终止错误
  • 动态规划(js版)

    1 动态规划算法介绍 理解动态规划 知乎好文 LeetCode简单的动态规划题 斐波那契数 爬楼梯 使用最小花费爬楼梯 有点小坑 不同路径 不同路径 II 注意初始值的设置 最小路径和 LeetCode较难的动态规划题 343 整数拆分 9