动态规划之背包问题中如何确定遍历顺序的问题-组合or排列?

news2024/12/22 20:20:41

关于如何确定遍历顺序

322. 零钱兑换中,本题求钱币最小个数,那么钱币有顺序和没有顺序都可以,都不影响钱币的最小个数

所以本题并不强调集合是组合还是排列。

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

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

在动态规划专题我们讲过了求组合数是动态规划:518.零钱兑换II (opens new window),求排列数是动态规划:377. 组合总和 Ⅳ (opens new window)。

所以本题的两个for循环的关系是:外层for循环遍历物品,内层for遍历背包或者外层for遍历背包,内层for循环遍历物品都是可以的!

那么我采用coins放在外循环,target在内循环的方式。

本题钱币数量可以无限使用,那么是完全背包。所以遍历的内循环是正序

综上所述,遍历顺序为:coins(物品)放在外循环,target(背包)在内循环。且内循环正序。

求组合数

例题:518.零钱兑换II

这是一道典型的背包问题,一看到钱币数量不限,就知道这是一个完全背包。

但本题和纯完全背包不一样,纯完全背包是凑成背包最大价值是多少,而本题是要求凑成总金额的物品组合个数!

注意题目描述中是凑成总金额的硬币组合数,为什么强调是组合数呢?

例如示例一:

5 = 2 + 2 + 1

5 = 2 + 1 + 2

这是一种组合,都是 2 2 1。

如果问的是排列数,那么上面就是两种排列了。

组合不强调元素之间的顺序,排列强调元素之间的顺序。 其实这一点我们在讲解回溯算法专题的时候就讲过了哈。

那我为什么要介绍这些呢,因为这和下文讲解遍历顺序息息相关!

  1. 确定遍历顺序

本题中我们是外层for循环遍历物品(钱币),内层for遍历背包(金钱总额),还是外层for遍历背包(金钱总额),内层for循环遍历物品(钱币)呢?

我在动态规划:关于完全背包,你该了解这些! (opens new window)中讲解了完全背包的两个for循环的先后顺序都是可以的。

但本题就不行了!

因为纯完全背包求得装满背包的最大价值是多少,和凑成总和的元素有没有顺序没关系,即:有顺序也行,没有顺序也行!

而本题要求凑成总和的组合数,元素之间明确要求没有顺序。

所以纯完全背包是能凑成总和就行,不用管怎么凑的。

本题是求凑出来的方案个数,且每个方案个数是为组合数。

那么本题,两个for循环的先后顺序可就有说法了。

我们先来看 外层for循环遍历物品(钱币),内层for遍历背包(金钱总额)的情况。

代码如下:

for (int i = 0; i < coins.size(); i++) { // 遍历物品
    for (int j = coins[i]; j <= amount; j++) { // 遍历背包容量
        dp[j] += dp[j - coins[i]];
    }
}

假设:coins[0] = 1,coins[1] = 5。

那么就是先把1加入计算,然后再把5加入计算,得到的方法数量只有{1, 5}这种情况。而不会出现{5, 1}的情况。

所以这种遍历顺序中dp[j]里计算的是组合数!

如果把两个for交换顺序,代码如下:

for (int j = 0; j <= amount; j++) { // 遍历背包容量
    for (int i = 0; i < coins.size(); i++) { // 遍历物品
        if (j - coins[i] >= 0) dp[j] += dp[j - coins[i]];
    }
}

背包容量的每一个值,都是经过 1 和 5 的计算,包含了{1, 5} 和 {5, 1}两种情况。

此时dp[j]里算出来的就是排列数!

可能这里很多同学还不是很理解,建议动手把这两种方案的dp数组数值变化打印出来,对比看一看!(实践出真知)

      2.举例推导dp数组

输入: amount = 5, coins = [1, 2, 5] ,dp状态图如下:

 求排列数

例题:377. 组合总和 Ⅳ

本题题目描述说是求组合,但又说是可以元素相同顺序不同的组合算两个组合,其实就是求排列!

弄清什么是组合,什么是排列很重要。

组合不强调顺序,(1,5)和(5,1)是同一个组合。

排列强调顺序,(1,5)和(5,1)是两个不同的排列。

但其本质是本题求的是排列总和,而且仅仅是求排列总和的个数,并不是把所有的排列都列出来。

如果本题要把排列都列出来的话,只能使用回溯算法爆搜

  1. 确定遍历顺序

个数可以不限使用,说明这是一个完全背包。

得到的集合是排列,说明需要考虑元素之间的顺序。

本题要求的是排列,那么这个for循环嵌套的顺序可以有说法了。

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

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

如果把遍历nums(物品)放在外循环,遍历target的作为内循环的话,举一个例子:计算dp[4]的时候,结果集只有 {1,3} 这样的集合,不会有{3,1}这样的集合,因为nums遍历放在外层,3只能出现在1后面!

所以本题遍历顺序最终遍历顺序:target(背包)放在外循环,将nums(物品)放在内循环,内循环从前到后遍历

2.举例来推导dp数组

class Solution {
    public int combinationSum4(int[] nums, int target) {
        // 初始化dp数组,dp[i]表示目标和为i的所有组合数
        int[] dp = new int[target + 1];
        
        // 目标和为0时只有一种情况,即不选择任何元素
        dp[0] = 1;
        
        // 遍历每个从0到target的值
        for (int i = 0; i <= target; i++) { //遍历背包
            // 对于每个值i,遍历数组中的每个数
            for (int j = 0; j < nums.length; j++) { //遍历物品
                // 如果当前值i大于等于数组中的数nums[j]
                if (i >= nums[j]) { //背包容量大于物品重量
                    // 将dp[i - nums[j]]的值加到dp[i]中
                    // 这意味着通过添加nums[j]到组合中,我们找到了新的组合数
                    //dp[i] += dp[i - nums[j]];
                    dp[i] = dp[i] + dp[i - nums[j]]; 
                }
            }
        }
        // 返回dp[target],即目标和为target的所有组合数
        return dp[target];
    }
}
  • 时间复杂度: O(target * n),其中 n 为 nums 的长度
  • 空间复杂度: O(target)

C++测试用例有两个数相加超过int的数据,所以需要在if里加上dp[i] < INT_MAX - dp[i - num]。

但java就不用考虑这个限制,java里的int也是四个字节吧,也有可能leetcode后台对不同语言的测试数据不一样。

总结

细心的同学看网上的题解,可能看一篇是遍历背包的for循环放外面,看一篇又是遍历背包的for循环放里面,看多了都看晕了,到底两个for循环应该是什么先后关系。

能把遍历顺序讲明白的文章几乎找不到!

这也是大多数同学学习动态规划的苦恼所在,有的时候递推公式很简单,难在遍历顺序上!

但最终又可以稀里糊涂的把题目过了,也不知道为什么这样可以过,反正就是过了。

那么这篇文章就把遍历顺序分析的清清楚楚。

动态规划:518.零钱兑换II (opens new window)中求的是组合数,动态规划:377. 组合总和 Ⅳ (opens new window)中求的是排列数。

而本题是要求最少硬币数量,硬币是组合数还是排列数都无所谓!所以两个for循环先后顺序怎样都可以!

这也是我为什么要先讲518.零钱兑换II 然后再讲本题即:322.零钱兑换,这是Carl的良苦用心那。

相信大家看完之后,对背包问题中的遍历顺序有更深的理解了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1698525.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据结构】二叉树的认识与实现

目录 二叉树的概念&#xff1a; 二叉树的应用与实现&#xff1a; 二叉树实现接口&#xff1a; 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树 二叉树节点个数​编辑 二叉树叶子节点个数 二叉树第k层节点个数 二叉树查找值为x的节点​编辑 二叉树前序遍…

Day 3:1738. 找出第 K 大的异或坐标值

Leetcode 1738. 找出第 K 大的异或坐标值 给你一个二维矩阵 matrix 和一个整数 k &#xff0c;矩阵大小为 m x n 由非负整数组成。 矩阵中坐标 (a, b) 的 值 可由对所有满足 0 < i < a < m 且 0 < j < b < n 的元素 matrix[i][j]&#xff08;下标从 0 开始计…

【Linux】进程通信实战 —— 进程池项目

送给大家一句话: 没有一颗星&#xff0c;会因为追求梦想而受伤&#xff0c;当你真心渴望某样东西时&#xff0c;整个宇宙都会来帮忙。 – 保罗・戈埃罗 《牧羊少年奇幻之旅》 &#x1f3d5;️&#x1f3d5;️&#x1f3d5;️&#x1f3d5;️&#x1f3d5;️&#x1f3d5;️ &a…

# linux 系统 没有 ifconfig 命令,提示: ifconfig: command not found

sudo ip route add default via 192.168.1.1 dev eth0# linux 系统 没有 ifconfig 命令&#xff0c;提示&#xff1a; ifconfig: command not found 一、问题描述&#xff1a; 有些伙伴在学习 linux 系统时&#xff0c;在 使用 ifconfig 命令 查询 系统 IP 出现 ifconfig: co…

【LakeHouse】Apache Iceberg + Amoro 助力网易构建云原生湖仓

Apache Iceberg Amoro 助力网易构建云原生湖仓 1.云原生湖仓背景与挑战2.Apache Iceberg 、Amoro 与云原生2.1 Apache Iceberg2.2 Amoro 简介 3.Apache Iceberg Amoro 云原生实践3.1 云上湖仓案例一3.2 云上湖仓案例二3.3 云上湖仓案例三 4.Amoro 未来发展规划 出品社区&…

上百个神奇有趣的小工具,总有一款适合你

这几天了不起在逛 GitHub 时&#xff0c;发现了一个有趣的项目——MikuTools。这是一个工具集合的 web 项目&#xff0c;提供了上百个有趣实用神奇的工具。 项目简介 MikuTools 项目主页的介绍很简单&#xff1a;一个轻量的工具集合。确实&#xff0c;项目界面非常简洁&#…

免费图片文字转换成文本,ocr文字识别软件免费版,真的太实用了!

截屏短视频上一段扎心文字&#xff0c;想把它发到朋友圈怎么办&#xff1f;这时候就需要一个OCR识别软件。 它就像一个聪明的小助手&#xff0c;它可以帮助电脑“看懂”书本上或者图片里的字。就像我们用眼睛看字一样&#xff0c;OCR软件用它的“眼睛”扫描图片&#xff0c;识…

企业活动想找媒体报道宣传怎样联系媒体?

在那遥远的公关江湖里,有一个传说,说的是一位勇士,手持鼠标和键盘,踏上了寻找媒体圣杯的征途。这位勇士,就是我们亲爱的市场部门小李,他的任务是为公司即将举行的一场盛大的企业活动找到媒体的聚光灯。 小李的故事,开始于一张空白的Excel表格,上面列着各大媒体的名称,旁边是一片…

面试题·栈和队列的相互实现·详解

A. 用队列实现栈 用队列实现栈 实现代码如下 看着是队列&#xff0c;其实实际实现更接近数组模拟 typedef struct {int* queue1; // 第一个队列int* queue2; // 第二个队列int size; // 栈的大小int front1, rear1, front2, rear2; // 两个队列的首尾指针 } MyS…

gfast:基于全新Go Frame 2.3+Vue3+Element Plus构建的全栈前后端分离管理系统

gfast&#xff1a;基于全新Go Frame 2.3Vue3Element Plus构建的全栈前后端分离管理系统 随着信息技术的飞速发展和数字化转型的深入&#xff0c;后台管理系统在企业信息化建设中扮演着越来越重要的角色。为了满足市场对于高效、灵活、安全后台管理系统的需求&#xff0c;gfast应…

ICQ 将于 6 月关闭,这是一种奇怪的方式,发现它在 2024 年仍然活跃

你知道ICQ还活着吗&#xff1f;不过&#xff0c;不要太兴奋;它将永远消失。 还记得ICQ吗&#xff1f;如果你这样做了&#xff0c;你可能会记得它是AOL在1998年购买的Messenger客户端&#xff0c;就在Yahoo Instant Messager和MSN Messenger加入竞争的时候。然后Skype出现了&…

网络模型-BFD与网络协议联动

一、BFD:双向转发检测 双向转发检测BFD(Bidirectional Forwarding Detection)是一种全网统一的检测机制&#xff0c;用于快速检测、监控网络中链路或者IP路由的转发连通状况。 1、BFD优点: 对相邻转发引擎之间的通道提供轻负荷、快速故障检测。这些故障包括接口数据链路&#…

g-h Filter 详细讲解

g-h 过滤器 g-h 滤波器百科介绍。 之前的翻译大家&#xff0c;我看都没什么阅读量&#xff0c;可能大家都不是很想看&#xff08;估计也是我英文太水&#xff09;。那么这篇博客我就先暂停直接翻译原文&#xff0c;而是直接说一下自己的理解。 本文章背后的书的详细介绍可以…

单片机原理及技术(二)—— AT89S51单片机(一)(C51编程)

目录 一、AT89S51单片机的片内硬件结构 二、AT89S51的引脚功能 2.1 电源及时钟引脚 2.2 控制引脚 2.3 并行 I/O口引脚 三、AT89S51的CPU 3.1 运算器 3.1.1 算术逻辑单元&#xff08;ALU&#xff09; 3.1.2 累加器A 3.1.3 程序状态字寄存器&#xff08;PSW&#xff09…

Linux-应用编程学习笔记(三、文件属性和目录)

一、文件类型 1、普通文件&#xff08;ls -l 文件&#xff0c;权限前边第一个"-"代表普通文件&#xff1b;stat 文件&#xff09; 文本文件&#xff1a;ASCII字符 二进制文件&#xff1a;数字0/1 2、目录文件&#xff08;‘’d&#xff09;&#xff1a;文件夹 3…

声压级越大,STIPA 越好,公共广播就越清晰吗?

在公共广播中&#xff0c;有些朋友经常问到是不是声压越大&#xff0c;广播清晰度就越高&#xff0c;下面我从搜集了一些专业技术资料&#xff0c;供大家参考。 一、声压级越大&#xff0c;STIPA 越好吗&#xff1f; 不完全是。最初&#xff0c;人们认为当声压级达到 60 dBA 以…

STL源码刨析:序列式容器之vector

目录 1.序列式容器和关联式容器 2.vector的定义和结构 3.vector的构造函数和析构函数的实现 4.vector的数据结构以及实现源码 5.vector的元素操作 前言 本系列将重点对STL中的容器进行讲解&#xff0c;而在容器的分类中&#xff0c;我们将容器分为序列式容器和关联式容器。本章…

氢燃料电池汽车行业发展

文章目录 前言 市场分布 整车销售 发动机配套 氢气供应 发展动能 参考文献 前言 见《氢燃料电池技术综述》 见《燃料电池工作原理详解》 见《燃料电池发电系统详解》 见《燃料电池电动汽车详解》 市场分布 纵观全球的燃料电池汽车市场&#xff0c;截至2022年底&#xff…

关于Iterator 和ListIterator的详解

1.Iterator Iterator的定义如下&#xff1a; public interface Iterator<E> {} Iterator是一个接口&#xff0c;它是集合的迭代器。集合可以通过Iterator去遍历集合中的元素。Iterator提供的API接口如下&#xff1a; forEachRemaining(Consumer<? super E> act…

阿里架构师整理:100套Java经典实战项目+源码!拿走玩去,练不会我直接退出IT

技术学习的目的是进行项目开发&#xff0c;但是很多同学苦于自学没有项目练手&#xff0c;被面试官问到项目经验&#xff0c;项目就成了自己的短板。小编特地收集了阿里架构师整理的java实战项目来满足大家的需求&#xff0c;让大家在实战中不断成长&#xff01; 话不多说了&…