力扣 | 背包dp | 279. 完全平方数、518. 零钱兑换 II、474. 一和零、377. 组合总和 Ⅳ

news2024/9/22 4:15:22

文章目录

  • 一、279. 完全平方数
  • 二、518. 零钱兑换 II
  • 三、474. 一和零
  • 四、377. 组合总和 Ⅳ

一、279. 完全平方数

LeetCode:279. 完全平方数
在这里插入图片描述
朴素想法:
这个题目最简单的想法是,可以用 O ( n n ) O(n\sqrt{}n) O(n n)的动态规划解决,定义dp[i]表示整数i完全平方数的最少数量。

由于我们不太能知道都有哪些可以构成,我们直接枚举 k k k,且满足 k 2 < = i k^2<=i k2<=i,找出最小的即可。

class Solution {
public:
    int numSquares(int n) {
        vector<int> dp(n + 1, 0x3f3f3f3f);
        dp[0] = 0;
        for(int i = 1; i <= n; ++ i){
            for(int k = 1; k <= sqrt(i); ++ k){
                dp[i] = min(dp[i], dp[i - k * k] + 1);
            }
        }
        return dp[n];
    }
};

问题抽象:
这个问题是否也能转化成 完全背包问题?

相当于 1 1 1~ n n n可以无限选择,达到背包容量n,并且使得放入的数最少。

我们可以定义 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示达到j,能够选择 1 1 1~ i i i,所使用的最小数字,那么我们就可以像完全背包一样进行状态转移了。(注意这里i最大是 n \sqrt{}n n,因此时间复杂度也是 n n n\sqrt{n} nn

我们仔细看一下 可以发现,实际上和方法一就是循环调个位置。

class Solution {
public:
    int numSquares(int n) {
        vector<int> dp(n + 1, 0x3f3f3f3f);
        dp[0] = 0;
        for(int k = 1; k <= sqrt(n); ++ k){
            for(int i = k * k; i <= n; ++ i){
                dp[i] = min(dp[i], dp[i - k * k] + 1);
            }
        }
        return dp[n];
    }
};

那么,我们如何抽象问题为完全背包问题? 到目前为止,我们还不能直接进行问题抽象,而是看到题有类似的样子才做出完全背包的想法。

我们来看看,之前考虑过的问题,整数拆分、322. 零钱兑换、518. 零钱兑换 II 。

他们都是 有无限个"物品"可以选择这些物品组合成"背包"里的物品(无论是体积还是质量)最后需要达成一个目标

这个目标就是我们定义的状态、
这个物品就是我们要规定什么时候能包含的、
这个背包就是总的容量

比如这个完全平方数,我们要定义的状态是和为i的最少完全平方数的数量,我们要规定的物品是 1 1 1~ n \sqrt{n} n ,我们的最后容量就是n

不过我们需要注意的是,放入背包的物品没有顺序要求。

二、518. 零钱兑换 II

LeetCode:518. 零钱兑换 II
在这里插入图片描述
普通的完全背包问题,做到这里已经不需要优化了,直接写了,因为写的完全背包太多了。

完全背包问题更新:
不过完全背包要注意的是,从小往大来遍历,因为可以选择无限个,
dp[j - coins[i]]这个转移,表示的是容量为j的这个背包一部分用来装一个coins[i],其余部分就是dp[j - coins[i]]的部分,这个部分是可以包含coins[i]的。 我们来思考一下这个转移对不对?我们来把背包具象化,dp[j - coins[i]]个容量为j - coins[i]的不同背包,它们都是互不相同的,且都可能包含coins[i]因为coins[i]没有个数限制。然后我们把这些不同的背包都加上coins[i]这个物品,这几个背包不同的! 不信你拿出来看看,一个一个比!这一部分是至少包含一个coins[i]的可能性。而之前的dp[j]是不包含任何coins[i]的可能性。

class Solution {
public:
    int change(int amount, vector<int>& coins) {
        vector<int> dp(amount + 1, 0);
        dp[0] = 1;

        for(int i = 0; i < coins.size(); ++ i){
            for(int j = coins[i]; j <= amount; ++ j){
                dp[j] = dp[j] + dp[j - coins[i]];
            }
        }

        return dp[amount];
    }
};

三、474. 一和零

LeetCode:474. 一和零
在这里插入图片描述
这是一个0-1背包问题,也就是,这里每个元素只能选择一次。
这里可以定义两维来表示01的个数,dp[i][j]表示最多有i0j1的最长子集长度。

那么我们来考虑当前考虑字符串时,我们提取出它的0的个数和1的个数,假如分别为kp,那么就可以进行一次转移。

dp[u][i][j] = max(dp[u - 1][i -k][j - p], dp[u - 1][i][j]

class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
        vector<vector<vector<int>>> dp(strs.size() + 1, vector<vector<int>>(m + 1, vector<int>(n + 1, 0)));

        for(int i = 1; i <= strs.size(); ++ i){
            int zero_num = Get(strs[i - 1]);
            int one_num = (int) strs[i - 1].size() - zero_num;
            
            for(int j = 0; j <= m; ++ j){
                for(int k = 0; k <= n; ++ k){
                    dp[i][j][k] = dp[i - 1][j][k];
                    if(j >= zero_num && k >= one_num)
                        dp[i][j][k] = max(dp[i][j][k], dp[i - 1][j - zero_num][k - one_num] + 1);
                }
            }
        }

        return dp[strs.size()][m][n];
    }
private:
    int Get(string & s){
        int ans = 0;
        for(auto ch : s){
            if(ch == '0') ++ ans;
        }
        return ans;
    }
};

空间优化:

class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
        vector<vector<int>> dp(m + 1, vector<int>(n + 1));

        for(int i = 1; i <= strs.size(); ++ i){
            int zero_num = Get(strs[i - 1]);
            int one_num = (int) strs[i - 1].size() - zero_num;
            
            for(int j = m; j >= zero_num; -- j){
                for(int k = n; k >= one_num; -- k){
                    dp[j][k] = max(dp[j][k], dp[j - zero_num][k - one_num] + 1);
                }
            }
        }

        return dp[m][n];
    }
private:
    int Get(string & s){
        int ans = 0;
        for(auto ch : s){
            if(ch == '0') ++ ans;
        }
        return ans;
    }
};

我们来看看空间优化先后的状态转移的区别,实际上空间优化后,它直接继承了所有的前面的部分。

空间优化前很容易出错:
容易写成这样:

for(int j = zero_num; j <= m; ++ j){
    for(int k = one_num; k <= n; ++ k){
        dp[i][j][k] = max(dp[i - 1][j][k], dp[i - 1][j - zero_num][k - one_num] + 1);
    }
}

仔细思考发现转移方程确实没错,dp[i][j][k]表示考虑前i个字符串,0的个数最多为j1的个数最多为k,我们单独拿出来strs[i - 1],其余部分是dp[i - 1][j - zero_num][k -one_num]这一部分也是对的定义,那么转移也是对的。那么问题出在哪呢?

问题出在,当 j < z e r o _ n u m j < zero\_num j<zero_num k < o n e _ n u m k < one\_num k<one_num时,我们没有进行状态转移,换句话说,没有考虑当当前背包容量不能容纳strs[[i - 1]时,可以放strs[0] ~ strs[i - 2],也就是说必须直接继承,因为考虑前i个字符串确实有东西可以放的话 你不继承就错了。

四、377. 组合总和 Ⅳ

LeetCode:377. 组合总和 Ⅳ
在这里插入图片描述
这里和零钱兑换II的区别在于,这里的元素是有顺序的。 零钱兑换II没有顺序,就像背包内放物品是没有顺序的。那么我们仅仅使用背包的思想肯定是不能得到答案的,毕竟这里有放入顺序。

既然有顺序,那我们枚举每一个容量的最后一个物品就能保证这几个的放法都不一样了,因此可以这样进行状态转移。

也就是说,我们从最小容量开始枚举它最后一个物品,来保证它放的内容不一样,然后依次枚举容量,你会发现我们保证了每个容量的最后一个都不一样,这确实能做到有顺序的问题。(排列问题,毕竟小容量也考虑了这些物品,这些物品可以无限次放。)

class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        vector<long long> dp(target + 1);
        dp[0] = 1;
        int mod = INT_MAX;
        for(int i = 1; i <= target; ++ i){
            for(int j = 0; j < nums.size(); ++ j){
                if(i >= nums[j])
                    dp[i] = (dp[i] + dp[i - nums[j]]) % mod;;
            }
        }

        return dp[target];
    }
};

题目中有一个坑的,就是说答案在int范围内,但是中间结果可能爆int,这里有两个解决方案:
(1)使用long long然后取模,long long是为了保证中间结果的计算爆int,取模是将结果映射到int范围内,对于答案来说必然是没有错误的,因为答案本身就在int范围内,映射过后还是本身。

(2)对于爆int的进行摒弃,原因在于,答案不可能用它来转移,这种背包不可能转移到其他人身上,不然会产生链锁反应,即使是转移了,那些人也必然对答案没有贡献,不然就爆int了。

        for (int i = 1; i <= target; i++) {
            for (int& num : nums) {
                if (num <= i && dp[i - num] < INT_MAX - dp[i]) {
                    dp[i] += dp[i - num];
                }
            }
        }

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

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

相关文章

OpenCV几何图像变换(1)映射转换函数convertMaps()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 将图像变换映射从一种表示形式转换为另一种表示形式。 该函数将用于 remap 的映射对从一种表示形式转换为另一种表示形式。以下选项 ((map1.type…

车辆类型检测算法、车辆类型源码及其样本与模型解析

车辆类型检测算法是利用计算机视觉和深度学习技术&#xff0c;对车辆图像进行自动分析和识别&#xff0c;以判断车辆的类型&#xff08;如轿车、SUV、货车等&#xff09;的一种算法。以下是对车辆类型检测算法的详细解析&#xff1a; 一、算法基础 车辆类型检测算法的基础是图…

区间预测|基于长短期记忆网络LSTM分位数单变量时间序列区间预测Matlab程序QRLSTM

区间预测|基于长短期记忆网络LSTM分位数单变量时间序列区间预测Matlab程序QRLSTM 文章目录 前言区间预测|基于长短期记忆网络LSTM分位数单变量时间序列区间预测Matlab程序QRLSTM 一、QRLSTM模型1. 基本原理1.1 LSTM (Long Short-Term Memory)1.2 量化回归&#xff08;Quantile …

移动端GenAI应用的崛起:从市场规模到成功案例分析

随着生成式人工智能&#xff08;GenAI&#xff09;技术的飞速发展&#xff0c;移动应用市场正经历一场前所未有的变革。从图像编辑到聊天机器人&#xff0c;这些基于AI的应用不仅满足了用户日益增长的需求&#xff0c;也为企业带来了巨大的商业机遇。本文将探讨这一领域的最新趋…

网站建设中:高效利用Robots.txt文件的策略与实践

原文&#xff1a;网站建设中&#xff1a;高效利用Robots.txt文件的策略与实践 - 孔乙己大叔 (rebootvip.com) 在网站中使用robots.txt文件是一种控制搜索引擎爬虫访问网站内容的方法。以下是关于如何在网站中使用robots.txt的详细步骤和注意事项&#xff1a; 一、创建robots.t…

集团数字化转型方案(四)

集团数字化转型方案通过全面部署人工智能&#xff08;AI&#xff09;、大数据分析、云计算和物联网&#xff08;IoT&#xff09;技术&#xff0c;创建了一个智能化的企业运营平台&#xff0c;涵盖从业务流程自动化、实时数据监控、精准决策支持&#xff0c;到个性化客户服务和高…

PV、UV、IP:网站流量分析的关键指标

原文&#xff1a;PV、UV、IP&#xff1a;网站流量分析的关键指标 - 孔乙己大叔 (rebootvip.com) 摘要&#xff1a; 在浩瀚的互联网海洋中&#xff0c;PV&#xff08;Page View&#xff0c;页面浏览量&#xff09;、UV&#xff08;Unique Visitor&#xff0c;独立访客数…

Eclipse SVN 插件在线下载地址

Eclipse SVN 插件 Subversive 在线安装 1、选择help下的install new software 2、点击 add 3、Name随便写&#xff0c;Location输入&#xff1a; https://download.eclipse.org/technology/subversive/4.8/release/latest/ 点击Add 4、然后一直下一步&#xff0c;Finish&am…

【QT】——1_QT学习笔记

一、QT是什么&#xff1f; QT 是一个功能强大、应用广泛的跨平台 C 应用程序开发框架&#xff0c;它不仅提供了丰富多样、美观实用的图形界面组件&#xff0c;还具备高效灵活的信号与槽通信机制&#xff0c;能够帮助开发者轻松构建出复杂且性能优越的应用程序&#xff0c;广泛…

VS Code中基于MSTest编写和运行测试

MS Test&#xff08;Microsoft Test Framework&#xff09;是微软提供的一个用于.NET应用程序的单元测试框架。以下是一个使用MS Test进行单元测试的示例&#xff0c;该示例将涵盖测试的基本步骤和概念。 项目搭建 在VS Code中开发C#时&#xff0c;创建solution&#xff08;解…

AI绘画Stable Diffusion画全身图总是人脸扭曲?ADetailer插件实现一键解决!商业级AI人物生成教程

大家好&#xff0c;我是灵魂画师向阳 你是否遇到过SD生成的人物脸部扭曲、甚至令人恶心的情况&#xff1f;也曾感到束手无策&#xff1f;别担心&#xff0c;这份教程专为你而来。 在使用SD生成人物全身照时&#xff0c;你可能经常发现人物的脸部会出现扭曲问题。这是因为人物…

整体思想以及取模

前言&#xff1a;一开始由于失误&#xff0c;误以为分数相加取模不能&#xff0c;但是其实是可以取模的 这个题目如果按照一般方法&#xff0c;到达每个节点再进行概率统计&#xff0c;但是不知道为什么只过了百分之十五的测试集 题目地址 附上没过关的代码 #include<bits…

如何在IIS中为typecho博客启用HTTPS访问

在上篇文章中&#xff0c;介绍了如何安装typecho博客系统&#xff0c;默认是没有启用https访问的&#xff0c;这篇文章介绍如何 在IIS中开启 https访问。 开启https访问需要两个步骤&#xff1a; 1、申请 一个ssl证书&#xff0c;我这里以阿里云上面的申请流程为例。其它云服务…

[Linux网络】基本网络命令socket编写TCP应用层实现简易计算器

W...Y的主页 &#x1f60a; 代码仓库分享&#x1f495; 前言&#xff1a;我们在上篇博客中学习了使用socket套接字完成了UDP的网络编程&#xff0c;今天我们继续使用套接字完成TCP的学习。 首先我们先来了解一些网络指令&#xff0c;让大家可以在实现网络编程后查看一些与网…

勇闯机器学习(第五关--中文文本特征提取)

以下内容皆为原创&#xff0c;制作实属不易&#xff0c;请点点关注和赞赞❥(^_^) 第一关&#xff1a;机器学习概念和流程http://t.csdnimg.cn/IuHh4第二关&#xff1a;数据集的使用http://t.csdnimg.cn/2jsdi第三关&#xff1a;特征工程-字典特征提取http://t.csdnimg.cn/ZpMt…

[数据集][目标检测]铁轨缺陷检测数据集VOC+YOLO格式4020张4类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;4020 标注数量(xml文件个数)&#xff1a;4020 标注数量(txt文件个数)&#xff1a;4020 标注…

高性能web服务器2——Nginx概述

Nginx 概述 Nginx 是一款广泛使用的高性能 Web 和反向代理服务器&#xff0c;以其出色的并发处理能力和低内存消耗而闻名。自 2004 年首次发布以来&#xff0c;Nginx 已经成为许多企业和组织的首选 Web 服务器和负载均衡解决方案。本文将对 Nginx 进行一个全面的概述&#xff…

探索Qotom Q51251OPS迷你电脑:功能与广泛应用

Qotom Q51251 OPS&#xff08;开放可插拔规范&#xff09;迷你电脑是一款设计紧凑且功能强大的设备&#xff0c;旨在满足不同领域的多样化需求。基于英特尔Core i5-12450H Alder Lake H处理器&#xff0c;这款设备不仅具备出色的计算性能&#xff0c;还提供了丰富的连接选项&am…

电路板中的MARK点

什么是mark点&#xff0c;什么情况下有mark点 Mark点的种类 局部mark点&#xff1a;针对那些 引脚数量众多 引脚间距非常紧凑的元器件 比如说QFT封装 BGA封装 MARK点的作用 不论是 拼版还是全局mark 一般都会放上2-3个点 第三个mark点一般用于比较大的电路板或者是比较大的拼…

接口加密解决方案,Python的各种加密实现!

01、前言 在现代软件开发中&#xff0c;接口测试已经成为了不可或缺的一部分。随着互联网的普及&#xff0c;越来越多的应用程序都采用了接口作为数据传输的方式。接口测试的目的是确保接口的正确性、稳定性和安全性&#xff0c;从而保障系统的正常运行。 在接口测试中&…