算法8.从暴力递归到动态规划1
算法|8.从暴力递归到动态规划1
目前感觉,背包问题和货币数组问题本质相同,货币的与dp相关的三种代码写完了,快复习不完了,背包暂时先不写了,回头再写,补充,再总结,结合那个C++大神的文章再弄。
背包类问题
目前来讲,我接触到的背包问题有四种分别是01背包、完全背包、多重背包、以及部分背包。背包问题都属于NP问题(非直接求解问题),前三种一般使用动态规划进行求解,后一种一般使用贪心求解。
01背包
题意:给定两个长度都为N的数组weights和values,weights[i]和values[i]分别代表 i号物品的重量和价值。给定一个正数bag,表示一个载重bag的袋子,装的物品不能超过这个重量。返回能装下的最大价值
解题思路:
- 先写出递归版本,然后对照着改dp。
- 要与不要。结果越大越好,是正向决策,同时使用的累加。
- 参数设置:重量数组w、价值数组v、当前决策到的坐标index、背包剩余的空间rest
- 可变参数为index和rest,所以dp表是一张二维表。
核心代码:
递归代码:
public static int maxValue(int[] w, int[] v, int bag) {if(w==null||v==null||w.length!=v.length||w.length==0){return 0;}return process(w,v,0,bag);
}public static int process(int[] w, int[] v, int index, int rest) {if(rest<0){return -1;}if(index==w.length){return 0;}int p1=process(w,v,index+1,rest);int p2=0;int next=process(w,v,index+1,rest-w[index]);if(next!=-1){p2=v[index]+next;}return Math.max(p1,p2);
}
dp代码:
public static int dp(int[] w, int[] v, int bag) {if (w == null || v == null || w.length != v.length || w.length == 0) {return 0;}int N=w.length;int[][] dp=new int[N+1][bag+1];for (int index = N-1; index >=0 ; index--) {for (int rest = 0; rest <=bag ; rest++) {int p1=dp[index+1][rest];int p2=0;int next=rest-w[index]<0?-1:dp[index+1][rest-w[index]];if(next!=-1){p2=v[index]+next;}dp[index][rest]=Math.max(p1,p2);}}return dp[0][bag];
}
测试代码:
public static void main(String[] args) {int[] weights = { 3, 2, 4, 7, 3, 1, 7 };int[] values = { 5, 6, 3, 19, 12, 4, 2 };int bag = 15;System.out.println(maxValue(weights, values, bag));System.out.println(dp(weights, values, bag));
}
测试结果:
完全背包
题意:有 N种物品和一个容量为C的背包,每种物品都有无限件。第 i件物品的体积是 v[i],价值是w[i] .求解将哪些物品装入背包可使这些物品的重量总和不超过背包容量,且价值总和最大。
解题思路:
核心代码:
测试代码:
测试结果:
多重背包
题意:有 N种物品和一个容量为C的背包,数量为s[i]。第 i件物品的体积是 v[i],价值是w[i] .求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
解题思路:
核心代码:
测试代码:
测试结果:
货币类问题
组成aim的方法数(每张认为是一种)
题意:arr是货币数组,其中的值都是正数。再给定一个正数aim。
每个值都认为是一张货币,即便是值相同的货币也认为每一张都是不同的,
返回组成aim的方法数。例如:arr = {1,1,1},aim = 2。
第0个和第1个能组成2,第1个和第2个能组成2,第0个和第2个能组成2
一共就3种方法,所以返回3
解题思路:
- 这一题其实和01背包问题很像,只是这个是要求正好组成aim,01背包则是不超过的方法数
- 所以这里我们只需要在aim=0时返回1,总金额超过了|根本就组不成(钱不够)就返回0
- 注意:改写过程中三目操作符建议加上括号(血淋淋的教训…)//
dp[index][rest] = dp[index + 1][rest] + (rest - arr[index]) < 0 ? 0 : dp[index + 1][rest - arr[index]];
核心代码:
暴力递归:
public static int coinWays(int[] arr, int aim) {return process(arr, 0, aim);
}public static int process(int[] arr, int index, int rest) {if (rest < 0) {return 0;}if (index == arr.length) {return rest == 0 ? 1 : 0;}return process(arr, index + 1, rest - arr[index])+ process(arr, index + 1, rest);
}
dp:
public static int dp(int[] arr, int aim) {if (aim == 0) {return 1;}int N = arr.length;int[][] dp = new int[N + 1][aim + 1];dp[N][0] = 1;for (int index = N - 1; index >= 0; index--) {for (int rest = 0; rest <= aim; rest++) {
// dp[index][rest] = dp[index + 1][rest] + (rest - arr[index]) < 0 ? 0 : dp[index + 1][rest - arr[index]];dp[index][rest] = dp[index + 1][rest] + (rest - arr[index] >= 0 ? dp[index + 1][rest - arr[index]] : 0);}}return dp[0][aim];}
测试代码:
略
测试结果:
组成aim的方法数(每种张数无限)
题意:arr是面值数组,其中的值都是正数且没有重复。再给定一个正数aim。
每个值都认为是一种面值,且认为张数是无限的。返回组成aim的方法数
例如:arr = {1,2},aim = 4 方法如下:1+1+1+1、1+1+2、2+2
一共就3种方法,所以返回3。
解题思路:
- 大体思路和上边相同,只不过子过程需要对要用多少张数进行遍历
- 张数遍历时循环条件为zhang * arr[index] <= rest,对应的dp改写中也需要遍历(如果不优化的,优化之后再说,这里应该是可以进行斜率优化)
核心代码:
递归:
public static int coinsWay(int[] arr, int aim) {if (arr == null || arr.length == 0 || aim < 0) {return 0;}return process(arr, 0, aim);
}public static int process(int[] arr, int index, int rest) {if(rest<0){return 0;}if(index==arr.length){return rest==0?1:0;}int ways=0;for (int zhang = 0; zhang*arr[index] < rest ; zhang++) {ways+=process(arr,index+1,rest-zhang*arr[index]);}return ways;
}
dp:
public static int dp(int[] arr, int aim) {if (arr == null || arr.length == 0 || aim < 0) {return 0;}int N = arr.length;int[][] dp = new int[N + 1][aim + 1];dp[N][0] = 1;for (int index = N - 1; index >= 0; index--) {for (int rest = 0; rest <= aim; rest++) {int ways=0;for (int zhang = 0; zhang*arr[index] < rest ; zhang++) {ways+=(rest-zhang*arr[index]<0)? 0: dp[index+1][rest-zhang*arr[index]];}dp[index][rest]=ways;}}return dp[0][aim];
}
测试代码:
略
测试结果:
组成aim的方法数(每种张数有限)
题意:arr是货币数组,其中的值都是正数。再给定一个正数aim。每个值都认为是一张货币,认为值相同的货币没有任何不同,返回组成aim的方法数
例如:arr = {1,2,1,1,2,1,2},aim = 4方法:1+1+1+1、1+1+2、2+2
一共就3种方法,所以返回3
解题思路:
- 本题思路与上题类似,只是张数变成有限的了,对应的遍历张数的条件多了一个
- 另外,本题不是给两个数组一个张数组和值数组,所以我们还需要对数据进行预处理,封装,并进行数据统计,并提供对应方法让外部调用。
- 封装构造方法初始化大小确定(我们给的);getInfo是我们进行的词频统计,根据arr,涉及到containKey,put等方法
核心代码:
递归代码:
public static class Info {private int[] coins;private int[] zhangs;public Info(int[] coins, int[] zhangs) {this.coins = coins;this.zhangs = zhangs;}
}public static Info getInfo(int[] arr) {HashMap<Integer, Integer> map = new HashMap<>();for (int num : arr) {if (map.containsKey(num)) {map.put(num, map.get(num) + 1);} else {map.put(num, 1);}}int N = map.size();int[] coins = new int[N];int[] zhangs = new int[N];int index = 0;for (Map.Entry<Integer, Integer> entry : map.entrySet()) {coins[index] = entry.getKey();zhangs[index] = entry.getValue();}Info info = new Info(coins, zhangs);return info;
}public static int coinsWay(int[] arr, int aim) {if (arr == null || arr.length == 0 || aim < 0) {return 0;}Info info = getInfo(arr);return process(info.coins, info.zhangs, 0, aim);
}public static int process(int[] coins, int[] zhangs, int index, int rest) {if (rest < 0) {return 0;}if (index == coins.length) {return rest == 0 ? 1 : 0;}int ways = 0;for (int zhang = 0; zhang < zhangs.length && (zhang * coins[index] < rest); zhang++) {ways += process(coins, zhangs, index + 1, rest - zhang * coins[index]);}return ways;
}
dp代码:
public static int dp(int[] arr, int aim) {if (arr == null || arr.length == 0 || aim < 0) {return 0;}Info info = getInfo(arr);int[] coins = info.coins;int[] zhangs = info.zhangs;int N = coins.length;int[][] dp = new int[N + 1][aim + 1];dp[N][0] = 1;for (int index = N - 1; index >= 0; index--) {for (int rest = 0; rest <= aim; rest++) {int ways = 0;for (int zhang = 0; zhang < zhangs.length && (zhang * coins[index] < rest); zhang++) {ways += (rest - zhang * coins[index] < 0) ? 0 : dp[index + 1][rest - zhang * coins[index]];}dp[index][rest] = ways;}}return dp[0][aim];
}
测试代码:略
测试结果:
组成aim的最小货币数(每张张数无限)
题意:arr是面值数组,其中的值都是正数且没有重复。再给定一个正数aim。
每个值都认为是一种面值,且认为张数是无限的。返回组成aim的最少货币数。
解题思路:
- 还是需要对张数进行遍历,只不过只有一个条件
- 接受结果值设为整数最大值,最终结果返回较小值
- 另外另外,边界条件不满足条件的值需要修改成最大值,不难咱们得犯大难了,遭老罪了!要不然你计算的时候把没有组成aim的,但是张数更少的算里边了,肯定错啊;rest==0时,不需要货币,即使满足也不需要了,所以记得改成0
补充:
- 这里其实可以对数组按照货币值进行预处理/排序(使用迭代给sort传比较器//优先级队列)
- 面值数组,不需要预处理,只需要用于降序排列的比较器
核心代码:
递归代码:
public static int minCoins(int[] arr, int aim) {if(aim==0){return 0;}if (arr == null || arr.length == 0 || aim < 0) {return Integer.MAX_VALUE;}return process(arr, 0, aim);
}public static int process(int[] arr, int index, int rest) {if (rest < 0) {return Integer.MAX_VALUE;}if (index == arr.length) {return rest == 0 ? 0 : Integer.MAX_VALUE;}int ans = Integer.MAX_VALUE;for (int zhang = 0; zhang * arr[index] < rest; zhang++) {int next = process(arr, index + 1, rest - zhang * arr[index]);if (next != Integer.MAX_VALUE && next > 0) {//要不然最大值加最大值可能滚成负数ans = Math.min(next, ans);}}return ans;
}
dp代码:
public static int dp(int[] arr, int aim) {if (aim == 0) {return 0;}int N = arr.length;int[][] dp = new int[N + 1][aim + 1];dp[N][0] = 0;for (int j = 1; j <= aim; j++) {dp[N][j] = Integer.MAX_VALUE;}for (int index = N - 1; index >= 0; index--) {for (int rest = 0; rest <= aim; rest++) {int ans = Integer.MAX_VALUE;for (int zhang = 0; zhang * arr[index] < rest; zhang++) {int next = (rest - zhang * arr[index] < 0) ? Integer.MAX_VALUE : dp[index + 1][rest - zhang * arr[index]];if (next != Integer.MAX_VALUE && next > 0) {//要不然最大值加最大值可能滚成负数ans = Math.min(next, ans);}}dp[index][rest]=ans;}}return dp[0][aim];
}
测试代码:略
测试结果:
从左到右尝试模型总结1
改写规则:
- 确定可变参数个数——dp是几维表
- 确定可变参数的变化范围——是0N还是0N-1
- 预处理(边界条件)
- 确定普遍位置怎么确定
- 边界判断——使用三目时一定要注意加括号😥
- 四个角中的哪个是最终结果
例题总结:
- 01背包——边界判断:超重是-1,没装够/刚好是0;要和不要的两种情况pk,要较小值
- 完全背包
- 多重背包
- 组成aim的方法数(每张认为是一种)——边界判断:超支/不够都是0,aim=0时index<=arr.length都算是1;两种情况不需要pk,直接相加返回
- 组成aim的方法数(每种张数无限)——边界判断:超支/不够都是0,aim=0时index<=arr.length都算是1;不是两种情况了,对有效的张数(一个条件)进行遍历,总方法相加
- 组成aim的方法数(每种张数有限)——边界判断:超支/不够都是0,aim=0时index<=arr.length都算是1;不是两种情况了,对有效的张数(两个条件)进行遍历,总方法相加
- 组成aim的最小货币数(每张张数无限)——边界条件判断:初值都为最大值,除了aim=0时,递归那aim=0一定在非法条件的前边,next值有效的判断;两种情况pk,要最小的
三种dp解法背包问题区别:
略
前三种dp解法货币数组区别:
注:这里返回的都是方法数,肯定是越多越好,不涉及边界值返回的系列问题。
- 货币数组类型决定了是否需要张数遍历(面值 不用)
- 张数有限无限决定了张数遍历的条件是1个还是两个
- 一般都是index倒序,rest正序
注:区分面值数组、货币数组
前者是天然去重,后者可能存在相同的,看题目设定决定是否需要进行预处理
另本类型开头的那种,其实也算是面值数组了。
两种情况了,对有效的张数(一个条件)进行遍历,总方法相加
- 组成aim的方法数(每种张数有限)——边界判断:超支/不够都是0,aim=0时index<=arr.length都算是1;不是两种情况了,对有效的张数(两个条件)进行遍历,总方法相加
- 组成aim的最小货币数(每张张数无限)——边界条件判断:初值都为最大值,除了aim=0时,递归那aim=0一定在非法条件的前边,next值有效的判断;两种情况pk,要最小的
三种dp解法背包问题区别:
略
前三种dp解法货币数组区别:
注:这里返回的都是方法数,肯定是越多越好,不涉及边界值返回的系列问题。
- 货币数组类型决定了是否需要张数遍历(面值 不用)
- 张数有限无限决定了张数遍历的条件是1个还是两个
- 一般都是index倒序,rest正序
注:区分面值数组、货币数组
前者是天然去重,后者可能存在相同的,看题目设定决定是否需要进行预处理
另本类型开头的那种,其实也算是面值数组了。
相关文章:

算法8.从暴力递归到动态规划1
算法|8.从暴力递归到动态规划1 目前感觉,背包问题和货币数组问题本质相同,货币的与dp相关的三种代码写完了,快复习不完了,背包暂时先不写了,回头再写,补充,再总结,结合那个C大神的文…...

8-JDBC 编程
目录 1.数据库编程的必备条件 PS:程序是怎么操作数据库的? 2.什么是JDBC? 2.1.JDBC定义 2.2.JDBC工作原理 3.JDBC使用 3.1.创建项目并添加MySQL驱动包 3.2.使用代码操作数据库 3.2.1.获得数据源 3.2.2.获得连接 3.2.3.获得执行器 …...

零基础如何学习 Web 安全?
Web安全不仅是互联网的核心,而且还是云计算和移动互联网的最佳载体。对于信息安全从业者而言,Web安全是一个非常重要的研究课题之一。 Web应用是指采用B/S架构、通过HTTP/HTTPS协议提供服务的统称。随着互联网的广泛使用,社交网络、聊天工具…...

【简单实用框架】【AddressablesMgr】【可移植】
☀️博客主页:CSDN博客主页💨本文由 萌萌的小木屋 原创,首发于 CSDN💢🔥学习专栏推荐:面试汇总❗️游戏框架专栏推荐:游戏实用框架专栏⛅️点赞 👍 收藏 ⭐留言 📝&#…...

android 12.0Launcher3禁止拖拽app图标到第一屏
1.概述 在12.0进行定制化开发Launcher3中,会对Launcher3 做些要求,比如现在的需求就是Launcher3第一屏的图标固定,不让其他屏的图标拖动到 第一屏所以说这个需求和 禁止拖拽图标到Hotseat类似,也是从WorkSpace.java里面寻找解决方案 2.Launcher3禁止拖拽app图标到第一屏相…...

SkyLine简介
简介 SkyLine产品系列(TerraExplorer 、TerraGate、TerraBuilder)是一套优秀的三维数字地球平台软件。凭借其国际领先的三维数字化显示技术,它可以利用海量的遥感航测影像数据、数字高程数据以及其他二三维数据搭建出一个对真实世界进行模拟…...

算法基础学习笔记——④前缀和\差分\双指针\位运算
✨博主:命运之光 ✨专栏:算法基础学习 目录 ✨前缀和 ✨一维前缀和 🍓一维前缀和模板: ✨二维前缀和 🍓二位前缀和模板: 前言:算法学习笔记记录日常分享,需要的看哈O(∩_∩)O&a…...

【Linux系统基础快速入门详解】Linux下安装软件必知必会4种方法(yum,编译安装,rpm包,二进制方式)等详解
在 Linux 下安装软件有多种方法可供选择,常用的包括 yum、编译安装、rpm 包和二进制方式。下面对这些方法进行详细说明: 使用 yum 安装软件yum 是 Red Hat 系列 Linux 发行版中常用的软件包管理工具,通过 yum 可以方便地安装、升级和删除软件包。yum 默认从官方仓库中下载软…...

ASEMI代理长电可控硅BT136参数,BT136规格,BT136说明
编辑-Z 长电可控硅BT136参数: 型号:BT136 RMS通态电流IT(RMS):6A 非重复浪涌峰值导通电流ITSM:25A 峰值栅极电流IGM:2A 平均栅极功耗PG(AV):0.5W 存储接点温度范围Tstg:-40 to 150℃ 工…...

代码线程安全
线程生命周期 synchronized synchronized会自动释放锁 synchronized同步代码块 synchronized后面括号里obj是锁对象(保证唯一);static修饰的obj对象是自定义MyThread线程类的静态成员变量,该自定义线程类所有实例共享保证锁对象唯一性;另一…...

Filebeat技术栈总结
filebeat 是一个轻量型日志采集器,本质上是一个 agent 。不依赖于任何应用,可以安装在任何节点上,可单独使用 Filebeat 并根据配置读取对应位置的日志进行上报和搜集。 filebeat 内置了常用的 output 组件,例如 kafka、ElasticSe…...

【App自动化测试】(十六)健壮性测试工具——Android Monkey
目录 1. 介绍2. 安装3. Monkey的使用4. money常用命令5. 常用事件类型参数6. Monkey使用参考 1. 介绍 Monkey是一个在模拟器或设备上运行的程序,用于生成用户事件的伪随机流。 为什么要使用Monkey这个自动化遍历工具? Monkey解决了一个测试痛点ÿ…...

实现第一个内核程序的Hello World
背景 在内核的开发中,总要先入个门。那么就要来编写第一个内核程序 入门 一个 module_init 程序是Linux内核模块的一部分,通过module_init 方法就能将程序载入内核。 module_init 方法需要以下步骤 编写module_init 的代码,并将其保存为…...

python基于协同过滤推荐算法的电影观后感推荐管理系统的设计
本课题所设计的影单管理系统,使用B/S架构,Python语言进行开发,它的优点代码不能从浏览器查看,保密性非常好,比其他的影单管理更具安全性。Python还容易修改和调试,毕竟影视是在不断发展过程中,难…...

Vue——状态管理库Pinia
写在前面:本文参考小满大牛的pinia专栏 一、Vuex与Pinia Vuex 和 Pinia 均是 Vue.js 的状态管理库,它们为 Vue 应用程序提供了一种集中式的、可预测的状态管理解决方案。 Vuex 是 Vue.js 官方推荐的状态管理库之一。它的核心概念包括 state、mutation…...

Linux:忘记root密码解决办法
如果你是虚拟机只要将光盘镜像连接到虚拟机上,以光盘iso镜像启动 如果你是真机或服务器那将实体u盘或实体光盘连接至设备并且以连接的设备启动 开机时候打断开机 使用 (u盘|光盘)引导启动 troubleshooting rescue a centos system 输入 1…...

Dockerfile(4) - RUN 指令详解
RUN 运行命令 shell 形式 命令在 shell 中运行Linux 上默认为 /bin/sh -cWindows 上 cmd /S /C RUN <command> exec 形式 RUN ["executable", "param1", "param2"] 必须双引号,不能是单引号 两种写法的实际栗子 RUN …...

一个完整的APP定制开发流程是怎样的?
随着移动互联网的发展,越来越多的 APP应用软件进入人们的生活,让我们的生活更便捷、更舒适。而随着互联网技术的进步,移动互联网应用软件开发行业也越来越成熟,为了适应市场需求,各种功能强大、性能良好的 APP应用软件…...

【数据结构】24王道考研笔记——线性表
线性表 目录 线性表定义和基本操作顺序表静态顺序表动态顺序表 链表单链表不带头结点:带头结点: 双链表循环链表循环单链表:循环双链表: 静态链表 顺序表链表比较逻辑结构:存储结构:基本操作: 定…...

【Linux C】基于树莓派/香橙派的蓝牙服务端——支持多蓝牙设备接入
一、需求 在树莓派/香橙派上利用开发板自带的蓝牙作为一个蓝牙服务端(主机),允许外来设备(从机)通过蓝牙接入进行通信,通信格式为透传方式;采用的编程语言为Linux C 二、环境准备 bluez安装 …...

鸿蒙App开发选择Java还是JavaScript?
众所周知, Java和 JavaScript是两种编程语言,这两种语言在不同的环境中都有许多用途。在鸿蒙 App开发中, Java和 JavaScript是两种常见的编程语言,它们都具有广泛的应用,并且都有其独特的优势。下面我们将就这两种编程…...

【Android】CountDownTimer的使用
android中怎么实现倒计时 在Android中,可以使用CountDownTimer类来实现倒计时。以下是一个简单的示例: javaCopy new CountDownTimer(30000, 1000) {public void onTick(long millisUntilFinished) {// 每次倒计时间隔1秒,更新UI上的倒计时剩…...

Linux :: 【基础指令篇 :: 文件及目录操作:(1)】:: ls :: 查看指定目录下的内容
前言:本篇是 Linux 基本操作篇章的内容! 笔者使用的环境是基于腾讯云服务器:CentOS 7.6 64bit。 学习集: C 入门到入土!!!学习合集Linux 从命令到网络再到内核!学习合集 目录索引&am…...

【商品详情 +关键词搜索】API 接口系列
首先,大家要到官方主页去申请一个 appkey,这个是做什么用的呢?App Key 是应用的唯一标识,TOP 通过 App Key 来鉴别应用的身份。AppSecret 是 TOP 给应用分配的密钥,开发者需要妥善保存这个密钥,这个密钥用来…...

RabbitMQ学习-发布确认高级
发布确认springboot版本 确认机制方案: 代码架构图: 配置文件: 在application.properties全局配置文件中添加spring.rabbitmq.publish-confirm-type属性,这个属性有以下几种值 none:禁用发布确认模式(默认)0 correlated:发布消…...

重载和内联函数
函数的默认参数 默认参数是指调用函数的时候,如果不写实参,那么将使用一个缺省值。 使用默认参数可以使你的函数更加灵活,同时减少了在不同上下文中为相同的参数重复编写相同的代码的需要。 return_type function_name(data_type paramete…...

从零学算法
198.你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 给定一个代表每个房屋存放金额…...

《Linux0.11源码解读》理解(四) head之重新设置IDT/GDT
上节提到,现在cs:ip指向0地址,此处存储着作为操作系统核心代码的system模块,是由head.s和 main.c以及后面所有源代码文件编译链接而成。head.s(以下简称head)紧挨着main.c,我们先执行head。 重新设置内核栈 _pg_dir: _startup_3…...

<SQL>《SQL命令(含例句)精心整理版(4)》
《SQL命令(含例句)精心整理版(4)》 14 数据库对象14.1 表14.2 视图14.3 存储过程14.3.1 概念14.3.2 创建存储过程14.3.2 调用存储过程14.3.3 DbVisualizer工具中调用方法14.3.3 DB2命令行脚本调用方法14.3.4 DB2中两个存储过程报错…...

C++死锁
死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的状态称为死锁。 死锁通常发生…...