01背包问题变种:从长度为n的数组里选出m个数使和为固定值sum

2023-11-18

这个问题是我从leetcode上一道问题所想到的,原题:如果是从数组中选出2个数相加使之成为固定的数sum,这当然很简单,把数组中的数字遍历一遍,判断另一个数字是否也在数组中即可。代码如下。

vector<int> twoSum(vector<int>& nums, int target) {  
    vector<int> result;  
    map<int, int> cache;//第一个为数字,第二个为下标  
    int max_index = nums.size()-1;  
    for (int i = 0 ; i <= max_index; i++)  
    {  
        cache[nums[i]] = i;  
    }  
      
    map<int, int>::iterator iter;  
      
    for (int i = 0 ; i <= max_index; i++)  
    {  
        iter = cache.find(target - nums[i]);  
          
        if(iter != cache.end() && iter->second != i)  
        {  
            result.push_back(nums[i]);  
            result.push_back(iter->first);  
            break;  
        }  
    }  
    return result;  
}  
那么如果是要从长度为n的数组中选出m个数使它们的和为固定值sum该怎么做呢?在解决这道问题之前,我们可以先从简单的做起,如果是要从长度为n的数组中选出部分数(不限数量)使他们的和为固定值sum,我们应该怎么做呢?
我原先的做法(错误的解法)是参 01背包,原题:有一个背包,能盛放的物品总重量为S,设有N件物品,其重量分别为w1,w2,…,wn,希望从N件物品中选择若干物品,所选物品的重量之和恰能放进该背包,即所选物品的重量之和即是S。
采用动态规划,dp[i]只有0或1两个值,1代表的是存在一些物品使得容量为i的背包恰好装满,0代表暂时还不存在有物品能够将背包恰好装满。如果容量为i的背包能够放满,那么p[i]中存放能够恰好把容量为i的背包放满的物品。
void CalSum(vector<int> &nums, int result)    
{  
    int len = nums.size();  
    int *dp = new int[result + 1];  
    dp[0] = 1;  
    for (int i = 1; i <= len; i++)  
    {  
        dp[i] = 0;  
    }  
    vector<int> *p = new vector<int>[result + 1];  
  
    for ( i = 0; i < len; i++)  
    {  
        for (int j = result; j >= nums[i]; j--)  
        {  
            if (dp[j] < dp[j - nums[i]])  
            {  
                dp[j] = dp[j - nums[i]];  
                p[j] = p[j-nums[i]];  
                p[j].push_back(nums[i]);  
            }  
        }  
    }  
    if (dp[result] == 1)//如果存在某些物品使得容量为result的背包恰好装满则输出。  
    {  
        for (vector<int>::iterator iter = p[result].begin(); iter != p[result].end(); iter++)  
        {  
            cout << *iter << " ";  
        }  
        cout << endl;  
    }  
    delete []dp;  
    delete []p;  
}  
这个解法用来解01背包问题,当然没有问题。但是如果是用来解这道题显然是不合适的。这个解法最大的限制就是nums数组中数字必须为正数,sum也必须为正数。结果可能是多种组合,而这种解法只能输出一种组合
后来发现在leetcode上面其实有类似的题:从一个的数组里面取出部分数,使这些数字的和为固定的数sum。我当时的做法是用递归遍历所有的组合,代码如下:
void combination(vector<int>& candidates, int start, int end, int target, vector<int> &tmp, vector<vector<int> > &result)  
{  
    if (target == 0)  
    {  
        result.push_back(tmp);  
        return;  
    }  
    if (start > end)//如果start超过end还没达到目标,那么就直接去掉  
    {  
        return ;  
    }  
    for (int i = start; i <= end; i++)  
    {  
        tmp.push_back(candidates[i]);  
        combination(candidates, i + 1, end, target - candidates[i], tmp, result);  
        tmp.pop_back();  
        while(i < end && candidates[i] == candidates[i+1])//去掉重复的组合  
        {  
            i++;  
        }              
    }  
}  
  
vector<vector<int> > CalSum(vector<int>& candidates, int target) {  
    sort(candidates.begin(), candidates.end());  
    vector<vector<int> > result;  
    vector<int> tmp;  
    int end = candidates.size() - 1;  
    combination(candidates, 0, end, target, tmp, result);  
    return result;  
}  

如果我们指定数字的个数m,只需要在push之前加一个判断:

if (target == 0)  
{  
    if (tmp.size == m)  
    {  
        result.push_back(tmp);  
    }  
    return;  
}  
其实在实际写算法的时候要尽量少用递归,因为 无节制的递归会造成堆栈的溢出
这里我参考了其他的人的非递归做法。比如数组中有10个数字 比如{-10,45,35,99,10,6,9,20,17,18} , sum为35,用二进制的0000000000~1111111111代表某个数字是否被选中,如果数字是0101010101代表45,99,6,20,18五个数字被选出来了。接着我们只需要计算着五个数是否等于我们要最终需要sum。代码如下:
网上有个评论说这个方法其实可以进行剪枝优化,原评论如下:
我们先对数字排个序 {-10, 6, 9, 10, 17, 18, 20, 35, 45, 99} , 当二进制数为 1001110000,已经算出35了那么1001110001-1001111111其实都是不用算的(肯定大于35),同样0001110000已经大于35了,可也需要不少次无用的循环校验,才能进位到0010000000,如果能把中间这些无用的循环略过,效率还能有很大提高!
根据这个评论提示,也就是如果10 0 1110000已经为35了那么下一个就是看10 1 0000000,如果1001 0 10000是35那么下一个看1001 1 00000。那么后面那个数字是怎么算出来的呢,我们可以发现这些数字的共同点就是最左边的1(可能是连续的)都被它们右边的1给代替了。如果前一个数为num,那么下一个数就为num | (num - 1) + 1。修改后的代码如下:
void CalSum(vector<int> &nums, int result)    
{  
    int len = nums.size();  
    int bit = 1 << len;  
    sort(nums.begin(), nums.end());//对数组排序  
    for (int i = 1; i < bit; )//从1循环到2^N    
    {    
        int sum = 0;    
        vector<int> tmp;  
        for (int j = 0; j < len; j++)    
        {    
            if ((i & 1 << j) != 0)//用i与2^j进行位与运算,若结果不为0,则表示第j位不为0,从数组中取出第j个数    
            {    
                sum += nums[j];    
                tmp.push_back(nums[j]);    
            }    
        }    
        if (sum == result)  
        {  
            i = i | (i - 1);//剪枝优化  
            for (vector<int>::iterator iter = tmp.begin(); iter != tmp.end(); iter++)  
            {  
                cout << *iter << " ";  
            }  
            cout << endl;  
        }  
        i++;  
    }    
}   
Ok,这样做乍一看没啥问题,后来仔细想想我被这个评论坑了,假如数组是{-8, -7 , -1, 1},sum为-15,当数字为1100时就已经算出-15了,按照评论,后面的1101、1110、1111是不用看的,其实我们看到1111算出来的值也是-15,后面的两个数一正一负恰好抵消。评论所说的优化只有在数组中的数全部为正数或者全部为负数才能够适用。在数组中的数字不确定正负时还是以第三个代码为准:)。
说了这么多了,咱们赶紧进入正题, 从长度为n的数组里选出m个数使和为固定值sum
我们可以在第三个代码的基础上修改,每选出一个二进制数,我们可以先计算这个二进制数中1的个数(也可以在后面计算)如果个数等于m,再对这个m个数相加看是否等于sum。 代码如下:
nt NumOf1(int num)  
{  
    int count = 0;  
    while (num)  
    {  
        num = num & (num - 1);  
        count++;  
    }  
    return count;  
}  
  
void CalSum(vector<int> &nums, int result, int m)    
{  
    int len = nums.size();  
    int bit = 1 << len;  
    for (int i = 1; i < bit; i++)//从1循环到2^N    
    {    
        int sum = 0;    
        vector<int> tmp;  
        if (NumOf1(i) == m)  
        {  
            for (int j = 0; j < len; j++)    
            {    
                if ((i & 1 << j) != 0)//用i与2^j进行位与运算,若结果不为0,则表示第j位不为0,从数组中取出第j个数    
                {    
                    sum += nums[j];    
                    tmp.push_back(nums[j]);    
                }    
            }    
            if (sum == result)  
            {  
                for (vector<int>::iterator iter = tmp.begin(); iter != tmp.end(); iter++)  
                {  
                    cout << *iter << " ";  
                }  
                cout << endl;  
            }  
        }  
    } }   
参考:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

01背包问题变种:从长度为n的数组里选出m个数使和为固定值sum 的相关文章

  • C语言/C++实现栈操作

    一 栈的概念 栈是一种常用的数据结构 它遵循先入后出 Last In First Out LIFO 的原则 栈的操作只在栈的一端进行 该端被称为栈顶 而另一端称为栈底 栈的基本操作包括压栈 入栈 push 和弹栈 出栈 pop 分别用于将元
  • Mysql 数据库

    数据库基础 1 什么是数据库 用来存储数据 数据库可在硬盘及内存中存储数据 数据库与文件存储数据的区别 数据库本质也是通过文件来存储数据 数据库的概念就是系统的管理存储数据的文件 数据库介绍 本质就是存储数据的C S架构的socket套接字
  • python 历险记(五)— python 中的模块

    目录 前言 基础 模块化程序设计 模块化有哪些好处 什么是 python 中的模块 引入模块有几种方式 模块的查找顺序 模块中包含执行语句的情况 用 dir 函数来窥探模块 python 的内置模块有哪些 结语 参考文档 系列文章列表 前言
  • 树06--二叉树中和为某一值的路径

    树06 二叉树中和为某一值的路径 jz24 题目概述 解析 参考答案 注意事项 说明 题目概述 算法说明 输入一颗二叉树的根节点和一个整数 按字典序打印出二叉树中结点值的和为输入整数的所有路径 路径定义为从树的根结点开始往下一直到叶结点所经
  • findBug 错误修改指南

    FindBugs错误修改指南 1 EC UNRELATED TYPES Bug Call to equals comparing different types Pattern id EC UNRELATED TYPES type EC c
  • DDP入门

    DDP 即动态动态规划 可以用于解决一类带修改的DP问题 我们从一个比较简单的东西入手 最大子段和 带修改的最大子段和其实是常规问题了 经典的解决方法是用线段树维护从左 右开始的最大子段和和区间最大子段和 然后进行合并 现在我们换一种方法来
  • DNG格式解析

    Author Maddock Date 2015 04 22 转载请注明出处 http www cnblogs com adong7639 p 4446828 html DNG格式基本概念 DNG格式是在TIFF的基础上扩展出来的 要了解D
  • 数据结构小白之插入排序算法

    1 插入排序 1 1 思路 将n个需要排序的元素看成两个部分 一个是有序部分 一个是无序部分 开始的时候有序表只有一个元素 无序表有n 1个元素 排序过程中每次从无序表中取出元素 然后插入到有序表的适当位置 从而成为新的有序表 类似排队 如
  • 数据结构之图的两种遍历实现(C语言版)

    上一期文章分享完了图的两种遍历方式 也是两种很重要的算法 DFS和BFS 这两种算法的应用和重要性我就不多说了 内行的人懂的都懂 今天这文章重要就是来上机实现这两种算法 又由于这两种算法都可以由邻接矩阵和邻接表来表示 博主分享的代码都是上机
  • 字符串09--表示数值的字符串

    字符串09 表示数值的字符串 jz53 题目概述 解析 参考答案 注意事项 说明 题目概述 算法说明 请实现一个函数用来判断字符串是否表示数值 包括整数和小数 例如 字符串 100 5e2 123 3 1416 和 1E 16 都表示数值
  • 堆栈01--用两个栈实现队列

    堆栈01 用两个栈实现队列 jz05 题目概述 解析 参考答案 注意事项 说明 题目概述 算法说明 用两个栈来实现一个队列 完成队列的Push和Pop操作 队列中的元素为int类型 测试用例 队列先进先出 输入 1 2 输出 1 2 解析
  • 如何防止过拟合和欠拟合

    过拟合和欠拟合是模型训练过程中经常出现的问题 两种情况正好相反 现将两者的定义及如何防止进行简要总结 1 过拟合 1 1 定义 是指模型对于训练数据拟合呈现过当的情况 反映到评估指标上就是模型在训练集上的表现很好 但是在测试集上的表现较差
  • 浮生六记

    浮生六记 目录 浮生六记卷一 闺房记乐 002 浮生六记卷二 闲情记趣 015 浮生六记卷三 坎坷记愁 022 浮生六记卷四 浪游记快 034 浮生六记 2 浮生六记卷一 闺房记乐 余生乾隆癸未冬十一月二十有二日 正值太平盛世 且在 衣冠之
  • 算法问题实战策略

    算法问题实战策略 基本信息作者 韩 具宗万 译者 崔盛一出版社 人民邮电出版社ISBN 9787115384621上架时间 2015 2 4出版日期 2015 年3月开本 16开页码 738版次 1 1 内容简介 算法问题实战策略 本书收录
  • 数据结构与算法-列表(双向链表)设计及其排序算法

    0 概述 本文主要涵盖列表 双向链表 的设计及其排序算法的总结 列表是一种典型的动态存储结构 其中的数据 分散为一系列称作节点 node 的单位 节点之间通过指针相互索引和访问 为了引入新节点或删除原有节点 只需在局部调整少量相关节点之间的
  • 时间复杂度+常见复杂度解释

    前言 算法的效率 虽然计算机能快速的完成运算处理 但实际上 它也需要根据输入数据的大小和算法效率来消耗一定的处理器资源 要想编写出能高效运行的程序 我们就需要考虑到算法的效率 算法的效率主要由以下两个复杂度来评估 时间复杂度 评估执行程序所
  • 插入排序超详解释,一看就懂

    目录 一 插入排序的相关概念 1 基本思想 2 基本操作 有序插入 二 插入排序的种类 三 直接插入排序 1 直接插入排序的过程 顺序查找法查找插入位置 2 使用 哨兵 直接插入排序 四 直接插入排序算法描述 五 折半插入排序 1 查找插入
  • 数组实现循环队列(增设队列大小size)

    目录 一 前言 1 如何实现循环 2 如何判断队列为空 3 如何判断队列为满 二 循环队列的结构定义 三 循环队列的创建及其初始化 四 入队 五 出队 六 取队头元素 七 取队尾元素 八 循环队列判空 九 循环队列判满 十 循环队列销毁 一
  • C++ AVL树(四种旋转,插入)

    C AVL树 四种旋转 插入 一 AVL树的概念及性质 二 我们要实现的大致框架 1 AVL树的节点定义 2 AVL树的大致框架 三 插入 1 插入逻辑跟BST相同的那一部分 2 修改平衡因子
  • 排序:计数排序

    一 概念 计数排序是非比较排序 是对哈希直接定址法的变形应用 二 思想 利用数组统计相同数据出现的次数 例如整型数据m出现n次 就在数组m位置记录数据为n 最后从头遍历数组打印数据即可 通俗来讲就是 数组下标即为数据 下标所指位置的值即为数

随机推荐

  • Python:每日一题之最少砝码

    问题描述 你有一架天平 现在你要设计一套砝码 使得利用这些砝码可以称出任意 小于等于 N 的正整数重量 那么这套砝码最少需要包含多少个砝码 注意砝码可以放在天平两边 输入格式 输入包含一个正整数 N 输出格式 输出一个整数代表答案 样例输入
  • (CMake) 指定生成器 generator

    文章目录 问题引入 具体处理 当前环境 例子 解决方案 命令行 设置变量 设置win环境变量 END 附录 win cmake 3 24 2 help linux cmake 3 10 2 help cmake基础 CMake 从下载到构建
  • Keil运行stm32项目无法打断点调试

    项目场景 有个新同事接了外协写的STM32F429的项目 项目接过来编译和烧录都没问题 但是Debug调试时候没法打断点 没有灰色区域可以点断点 点击运行可以 但点暂停也没有停止黄色光标 debug模式下就如同这样 1 问题描述 根据上述现
  • 关于APT32C001ADC采集不准的问题说明

    因为之前开发一款产品 要使用到触摸按键 又不想新增一个触摸IC 所以选择了APTC001进行开发 但是在调试的时候发现ADC有时候会不准 有时候是0电压的 但读寄存器的值却不是零 有时候读电源电压 那应该是4096的 但实际采集回来的去不是
  • 【毕业设计】机器视觉手势检测和识别系统 - python 深度学习

    文章目录 0 前言 1 实现效果 2 技术原理 2 1 手部检测 2 1 1 基于肤色空间的手势检测方法 2 1 2 基于运动的手势检测方法 2 1 3 基于边缘的手势检测方法 2 1 4 基于模板的手势检测方法 2 1 5 基于机器学习的
  • 浅述SATA接口Raid、AHCI、IDE三种模式

    今天在一台计算机上插上CF卡 不能工作 CF卡灯不亮 进BIOS SATA mode从IDE改成AHCI就好了 首先说一下 关于主板的SATA接口的工作模式 BIOS中常见的选项有以下三种 RAID 部分技嘉主板叫XHD AHCI IDE
  • Java String 常用操作方法说明和使用

    ps Java中的String类是一个非常重要的类 在Java程序中广泛使用 它可以用来保存和操作字符串 在这篇博客中 我们将对Java String的所有操作方法进行说明和使用 1 1 使用双引号创建字符串 String str1 Hel
  • ObjectC基础之注释、关键字、数据类型

    一 OC的注释 OC的注释不是 或者 了 它的注释是 举个例子 这是被注释掉的内容 二 OC的关键字 上图我们比较陌生的有 register typedef extern union unsigned const signed goto v
  • 自定义函数实现字符串处理函数strcat、 strcpy、strcmp、strlen和strlwr

    编C语言程序 用自定义函数实现字符串处理函数strcat strcpy strcmp strlen和strlwr的功能 strlen char str int n 0 char p str while p n return n strcat
  • 蓝桥杯 题库 简单 每日十题 day2

    01 卡片 题目描述 本题为填空题 只需要算出结果后 在代码中使用输出语句将所填结果输出即可 小蓝有很多数字卡片 每张卡片上都是数字 0 到 9 小蓝准备用这些卡片来拼一些数 他想从 1 开始拼出正整数 每拼一个 就保存起来 卡片就不能用来
  • 源 “MySQL 8.0 Community Server“ 的 GPG 密钥已安装,但是不适用于此软件包。请检查源的公钥 URL 是否配置正确。

    源 MySQL 8 0 Community Server 的 GPG 密钥已安装 但是不适用于此软件包 请检查源的公钥 URL 是否配置正确 只需将 sudo yum install y mysql server 改为 sudo yum i
  • Linux如何查看进程、杀死进程、启动进程等常用命令

    1 查进程 ps命令查找与进程相关的PID号 ps a 显示现行终端机下的所有程序 包括其他用户的程序 ps A 显示所有程序 ps c 列出程序时 显示每个程序真正的指令名称 而不包含路径 参数或常驻服务的标示 ps e 此参数的效果和指
  • Java虚拟机二:JVM性能调优

    堆空间的划分 Java中的堆是JVM所管理的最大的一块内存空间 主要用于存放各种类的实列对象 这样划分的目的是为了使JVM能够更好的管理堆内存中的对象 堆的内存划分如图 Java堆的内存划分如图所示 分别为年轻代 Old Memory 老年
  • 【burpsuite安全练兵场-服务端4】操作系统命令注入-5个实验(全)

    前言 介绍 博主 网络安全领域狂热爱好者 承诺在CSDN永久无偿分享文章 殊荣 CSDN网络安全领域优质创作者 2022年双十一业务安全保卫战 某厂第一名 某厂特邀数字业务安全研究员 edusrc高白帽 vulfocus 攻防世界等平台排名
  • 等保三级安全要求简要攻略-安全通信网络和安全区域边界

    之前有两篇文章写了为什么要做等保测评 等保测评的含义以及等保测评这份工作的一些职责和一些常见的FAQ 没有看过的朋友可以先去看下我的另外两篇文章 一起聊聊等保测评 和 一起聊聊等保测评工作内容以及FAQ 今天我们来攻略一下等保2 0国家标准
  • QT Webkit的插件Plugin设计实现

    Qt Webkit中浏览器插件Plugin设计实现是我们要介绍的内容 我们都知道浏览器中有一套由Netscape浏览器传承下来的插件接口 包括webkit firefox都是支持的 但是那个开发起来比较困难 并且是平台相关的 借助于Qt的跨
  • 用BeanUtils类自动封装表单数据

    导入架包 commons beautils 1 8 0 jar commons logging jar Class
  • 阿里前端笔试的五道算法题

    根据传入字符串创建一个循环输出字符的方法 每次调用该方法时返回下一个字符 全部返回完毕后从头部重新开始 function loopString str var i 1 function loopString1 i if i
  • Oracle客户端安装环境变量

    客户端安装好后须在所安装路径下client中插入NETWOKE文件夹中的ADMIN 文件tnsnames ora 显示Database方可登陆使用 使用之前在命令处进行连接测试
  • 01背包问题变种:从长度为n的数组里选出m个数使和为固定值sum

    这个问题是我从leetcode上一道问题所想到的 原题 如果是从数组中选出2个数相加使之成为固定的数sum 这当然很简单 把数组中的数字遍历一遍 判断另一个数字是否也在数组中即可 代码如下 vector