LeetCode 474. Ones and Zeroes 动态规划解法+拓展

问题来源

此题来源于LeetCode 474. Ones and Zeroes
在写这篇之前,我百度了一下这道题,发现已经有很多人写过这个问题了,然而大多数只是为了答题而答题,给出了代码,很少有文字解释的,也很少有深入拓展的。因此,我这次来给出一个比较详尽的版本,并且在最后对结果进行了拓展。

问题简介

已知一个字符串数组,数组内的字符串都是仅由0和1组成的,现在给定m个0和n个1,试问这m个0和n个1最多可以组成几个数组中的字符串。
比如:

Input: Array = {"10", "0001", "111001", "1", "0"}, m = 5, n = 3
Output: 4Explanation: This are totally 4 strings can be formed by the using of 5 0s and 3 1s, which are “10,”0001”,”1”,”0”

又比如:

Input: Array = {"10", "0", "1"}, m = 1, n = 1
Output: 2Explanation: You could form "10", but then you'd have nothing left. Better form "0" and "1".

解决方案

这是一个非常典型的二维0/1背包问题,相当于是在问我们有一个背包的空间大小为m,最大载重为n,给定k个物品,已知每个物品的大小和重量,试问最多能放进多少个物品(每个物品只能放一次)。
该问题的状态方程为

f(m,n,k)=max(f(m,n,k−1),1+f(m−i,n−j,k−1))f(m,n,k) = max(f(m,n,k-1),1+f(m-i,n-j,k-1)) f(m,n,k)=max(f(m,n,k1),1+f(mi,nj,k1))

f(m,n,k)f(m,n,k)f(m,n,k)是指在限制为(m,n)(m,n)(m,n)的情况下,考虑前kkk个字符串所能得到的最多字符串的个数。
这个式子的意思是,我们从放第1个字符串开始考虑,直到第k个字符串,如果在限制为(m,n)(m,n)(m,n)的情况下,放这个字符串进去比不放这个字符串得到的个数要多,那么我们就放这个字符串进去,否则不放。
如果想直接看动态规划在这里最简洁的解法,请直接跳到解法3,有耐心的话就一步步看下去吧。

解法1

时间复杂度:O(m⋅n⋅k)O(m \cdot n \cdot k)O(mnk)
空间复杂度:O(m⋅n⋅k)O(m \cdot n \cdot k)O(mnk)
其中,mmm为0的个数, nnn为1的个数,kkk为已知字符串数组的长度strs.size()
这种解法虽然很浪费空间,但是保存了每种情况的状态,只有在这种情况下,我们才能逆推出这个最大长度是由哪些字符串组成的。

class Solution {
private:vector<vector<vector<int>>> rec;int strsN;private://计算每个字符串有几个0和几个1pair<int, int> countNums(string s){int os = 0;int zs = 0;for (int i = 0; i < s.length(); i++){if ('0' == s[i])zs++;}os = s.length() - zs;return make_pair(zs, os);}public:int findMaxForm(vector<string>& strs, int m, int n) {strsN = strs.size();//创建一个m*n*strN的数组来存放每种情况下的状态rec = vector<vector<vector<int>>>(m + 1, vector<vector<int>>(n + 1, vector<int>(strsN, 0)));for (int count = 0; count < strsN; count++){pair<int, int> p = countNums(strs[count]);for (int i = m; i >= 0; i--){for (int j = n; j >= 0; j--){if (i >= p.first && j >= p.second)rec[i][j][count] = (count == 0 ? 1 : max(rec[i][j][count - 1], 1 + rec[i - p.first][j - p.second][count - 1]));elserec[i][j][count] = (count == 0 ? 0 : rec[i][j][count - 1]);}}}return rec[m][n][strsN - 1];}
};

解法2

时间复杂度:O(m⋅n⋅k)O(m \cdot n \cdot k)O(mnk)
空间复杂度:O(m⋅n⋅2)O(m \cdot n \cdot 2)O(mn2),即O(m⋅n)O(m \cdot n)O(mn)
其中,mmm为0的个数, nnn为1的个数,kkk为已知字符串数组的长度strs.size()
然后,我们发现其实每次更新状态kkk时仅仅用到了上一次的状态k−1k-1k1,所以我们可以将存储状态的数组降成m⋅n⋅2m \cdot n \cdot 2mn2的大小。

class Solution {
private:vector<vector<vector<int>>> rec;private://计算每个字符串有几个0和几个1pair<int, int> countNums(string s){int os = 0;int zs = 0;for (int i = 0; i < s.length(); i++){if ('0' == s[i])zs++;}os = s.length() - zs;return make_pair(zs, os);}public:int findMaxForm(vector<string>& strs, int m, int n) {//创建一个m*n*2的数组来存放每种情况下的状态rec = vector<vector<vector<int>>>(m + 1, vector<vector<int>>(n + 1, vector<int>(2, 0)));for (int count = 0; count < strs.size(); count++){pair<int, int> p = countNums(strs[count]);//设置level来让rec[i][j][0]和rec[i][j][1]轮流变成上一组的状态int level = count % 2;for (int i = m; i >= 0; i--){for (int j = n; j >= 0; j--){                                        if (i >= p.first && j >= p.second)if (0 == level)rec[i][j][0] = max(rec[i][j][1], 1 + rec[i - p.first][j - p.second][1]);elserec[i][j][1] = max(rec[i][j][0], 1 + rec[i - p.first][j - p.second][0]);elseif (0 == level)rec[i][j][0] = rec[i][j][1];elserec[i][j][1] = rec[i][j][0];}}}return max(rec[m][n][0], rec[m][n][1]);}
};

解法3

时间复杂度:O(m⋅n⋅k)O(m \cdot n \cdot k)O(mnk)
空间复杂度:O(m⋅n)O(m \cdot n)O(mn)
其中,mmm为0的个数, nnn为1的个数,kkk为已知字符串数组的长度strs.size()
然后,我们又再次发现,其实我们把上一次的状态和这次的状态放在同一个数组中就可以了!因为更新时是从后往前的,要用到的上一次的值并没有受到影响,于是又有了如下解法

class Solution {
private:vector<vector<int>> rec;private://计算每个字符串有几个0和几个1pair<int, int> countNums(string s){int os = 0;int zs = 0;for (int i = 0; i < s.length(); i++){if ('0' == s[i])zs++;}os = s.length() - zs;return make_pair(zs, os);}public:int findMaxForm(vector<string>& strs, int m, int n) {//设置一个二维数组来记录状态rec = vector<vector<int>>(m + 1, vector<int>(n + 1, 0));for (int count = 0; count < strs.size(); count++){pair<int, int> p = countNums(strs[count]);int level = count % 2;for (int i = m; i >= p.first; i--){for (int j = n; j >= p.second; j--){                                        rec[i][j] = max(rec[i][j], 1 + rec[i - p.first][j - p.second]);}}}return rec[m][n];}
};

拓展——输出某组解

如果仅仅是针对问题本身的话,解法3自然是最理想的一种解法。但是,如果我们想知道这个最大长度的字符串组是由哪些字符串组成的又该怎么办呢?这个时候,就要用解法1记录了所有状态的数组逆推了~
下面给出的代码只能找到其中的一组解,并不能找到所有解,因为可能有很多种情况。找所有解的方法只需在这之上拓展一下即可,不过不要忽略了重复解的情况,这是一个难点~

class Solution {
private:vector<vector<vector<int>>> rec;int strsN;private:pair<int, int> countNums(string s){int os = 0;int zs = 0;for (int i = 0; i < s.length(); i++){if ('0' == s[i])zs++;}os = s.length() - zs;return make_pair(zs, os);}public:void findMaxForm(vector<string>& strs, int m, int n) {strsN = strs.size();rec = vector<vector<vector<int>>>(m + 1, vector<vector<int>>(n + 1, vector<int>(strsN, 0)));for (int count = 0; count < strsN; count++){pair<int, int> p = countNums(strs[count]);for (int i = m; i >= 0; i--){for (int j = n; j >= 0; j--){if (i >= p.first && j >= p.second)rec[i][j][count] = (count == 0 ? 1 : max(rec[i][j][count - 1], 1 + rec[i - p.first][j - p.second][count - 1]));elserec[i][j][count] = (count == 0 ? 0 : rec[i][j][count - 1]);}}}}vector<string> getOneSol(vector<string> strs, int m, int n){//调用findMaxForm()把状态存到rec中findMaxForm(strs, m, n);vector<string> res;int zs = m;int os = n;for (int i = strsN - 1; i >= 1; i--){pair<int, int> p = countNums(strs[i]);if (rec[zs][os][i] == rec[zs][os][i - 1])continue;else{res.push_back(strs[i]);zs -= p.first;os -= p.second;}if (zs <= 0 && os <= 0)break;}pair<int, int> p = countNums(strs[0]);if (p.first <= zs && p.second <= os)res.push_back(strs[0]);return res;}
};

结束语

很多动态规划的问题都可以演变成背包问题,因此掌握背包问题的本质是非常重要的。
如有不足,还请指正~

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

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

相关文章

常用的几种编程语言的介绍

编程语言&#xff08;programming language&#xff09;&#xff0c;来自百度百科的解释为&#xff1a;编程语言是用来定义计算机程序的形式语言。它是一种被标准化的交流技巧&#xff0c;用来向计算机发出指令。一种计算机语言让程序员能够准确地定义计算机所需要使用的数据&a…

三门问题(Monty Hall problem)背后的贝叶斯理论

文章目录1 前言2 问题简介3 直观的解释4 贝叶斯理论的解释1 前言 三门问题可以说有着各种版本的解释&#xff0c;但我看了几个版本&#xff0c;觉得没有把其中的条件说清楚&#xff0c;所以还是决定按照自己的理解记录一下这个特别有意思的问题。 2 问题简介 三门问题&#…

一、在vue项目中使用mock.js(详解)

步骤1.搭建测试项目 步骤1.1创建项目 命令&#xff1a; vue create mock-demo 步骤1.2安装依赖 命令&#xff1a; #使用axios发送ajax cnpm install axios--save #使用mockjs产生随机数据 cnpm install mockjs--save-dev #使用json5解决ison文件&#xff0c;无法添加注释…

matlab如何建立一个空矩阵,然后往里面赋值

1 x1:0.1:10; 2 y[]; 3 for i1:length(x) 4 % y[y;x(i)];%把每一个x都放到Y里&#xff0c;成为一列 5 y[y,x(i)];%把每一个x都放到Y里&#xff0c;成为一行 6 end 转载于:https://www.cnblogs.com/sddai/p/5410821.html

二、在jQuery中使用mockjs

在jQuery项目中使用mock.js 步骤1.搭建项目 步骤1.1创建项目 新建文件夹jquery-mock-demo 步骤1.2新建index.html&#xff0c;引入jquery.js文件和mock.js <!DOCTYPE html> <html><head><meta charset"utf-8"><title></title&g…

决策树相关知识小结

前言 本文是一篇关于决策树方面知识的小结&#xff0c;不包含具体的例子&#xff08;想看例子推荐文献[1]的第4章&#xff09;&#xff0c;主要总结了ID3、C4.5和CART树三者的区别&#xff0c;剪枝处理&#xff0c;连续值和缺失值的处理。 决策树的基本算法 决策树的学习目的…

Android 中如何计算 App 的启动时间?

&#xff08;转载&#xff09; 已知的两种方法貌似可以获取&#xff0c;但是感觉结果不准确&#xff1a;一种是&#xff0c;adb shell am start -w packagename/activity,这个可以得到两个值&#xff0c;ThisTime和TotalTime&#xff0c;不知道两个有什么区别&#xff0c;而且与…

项目实战:Express实现学生管理系统(CRUD)

一、起步 初始化 npm init -y 生成package.json文件 模板处理 cnpm install express --save cnpm install bootstrap --savenpm install --save art-template npm install --save express-art-template//两个一起安装 npm i --save art-template express-art-template文件目录…

2016年度 JavaScript 展望(下)

【编者按】本文作者为资深 Web 开发者 TJ VanToll, TJ 专注于移动端 Web 应用及其性能&#xff0c;是《jQuery UI 实践》 一书的作者。 本文系 OneAPM 工程师编译呈现&#xff0c;以下为正文的第二部分。点此阅读第一部分。 本地移动 apps 在2015年&#xff0c;出现了一种新的基…

LeetCode547. Friends Circles 利用union find | bfs | dfs三种方法解决

问题来源 此题来源于LeetCode547. Friend Circles&#xff0c;主要运用了并查集&#xff08;union find&#xff09;、广度优先遍历&#xff08;bfs&#xff09;和深度优先遍历&#xff08;bfs&#xff09;三种方法解决。 问题简述 给定一个NN的矩阵M表示了N个人之见的朋友关…

Python模块之MyQR——制作个性化动态二维码(超详细)

一、首先安装MyQR 第一种方式&#xff1a;cmd 命令行输入 pip install MyQR 第二种方式&#xff1a;pyCharm中手动选择进行安装 二、 myqr.run() 函数里面的参数 三、编写代码 # encodingutf-8 from MyQR import myqrmyqr.run(words"https://blog.csdn.net/weixin_448…

地址选择控件开发-

先睹为快 闲话少说&#xff0c;我们先来看看今天我们研究的控件的最终效果图(参照天猫的送货地址设置的效果)&#xff1a; “地址选择Web控件”的基本组成&#xff1a; 使用控件举例&#xff1a; <!--需要加载和引用的文件--> <link rel"stylesheet" href&q…

逻辑回归(logistic regression)的本质——极大似然估计

文章目录1 前言2 什么是逻辑回归3 逻辑回归的代价函数4 利用梯度下降法求参数5 结束语6 参考文献1 前言 逻辑回归是分类当中极为常用的手段&#xff0c;因此&#xff0c;掌握其内在原理是非常必要的。我会争取在本文中尽可能简明地展现逻辑回归(logistic regression)的整个推导…

使用mongoose 在 Node中操作MongoDB数据库

MongoDB 关系型和非关系型数据库 关系型数据库&#xff08;表就是关系&#xff0c;或者说表与表之间存在关系&#xff09;。 所有的关系型数据库都需要通过sql语言来操作所有的关系型数据库在操作之前都需要设计表结构而且数据表还支持约束 唯一的主键默认值非空 非关系型…

回调和异步

// 回调函数&#xff1a;一个函数中调用传入的另一个函数 // 这里的callback就是个回调函数 function we(callback,something){something" is cool";callback(something); }function learn(something){console.log(something); }we(learn,"node.js"); //第…

pip install时发生raise ReadTimeoutError(self._pool, None, 'Read timed out.')的解决方案

问题描述 在windows下&#xff0c;python在安装一些包的时候&#xff0c;常常会碰到time out的情况&#xff0c;如下图所示。不管再重试几次都是如此&#xff0c;这时候该怎么办呢&#xff1f; 解决方案 方案一&#xff1a;更换安装源&#xff08;推荐使用豆瓣源&#xff09…

使用Node 操作MySQL数据库

1. 安装 官方文档&#xff1a;https://www.npmjs.com/package/mysql npm install --save mysql2.代码演示 var mysql require(mysql);// 1.创建连接 var connection mysql.createConnection({host : localhost,user : root,password : root,database : my_db …

利用随机森林对特征重要性进行评估

文章目录1 前言2 随机森林&#xff08;RF&#xff09;简介3 特征重要性评估4 举个例子5 参考文献1 前言 随机森林是以决策树为基学习器的集成学习算法。随机森林非常简单&#xff0c;易于实现&#xff0c;计算开销也很小&#xff0c;更令人惊奇的是它在分类和回归上表现出了十…

个人博客总结3

一&#xff0e;昨天做了什么&#xff1f; 昨天利用安卓连接数据库完成一个图片的保存的程序例子&#xff0c;为我们的团队作业刷脸将人脸图片保存到数据中打下基础。 创建了一个相片和数据库保存和查询的工程创建了一个数据库然后将图片保存到数据库中&#xff0c;先将图片转化…

Promise学习——解决回调地狱问题

Promise promise 容器概念&#xff1a; callback hell&#xff08;回调地狱&#xff09;: 文件的读取无法判断执行顺序&#xff08;文件的执行顺序是依据文件的大小来决定的&#xff09;(异步api无法保证文件的执行顺序) var fs require(fs);fs.readFile(./data/a.text,ut…