每日一练——从长度为n的数组里选出m个数使和为固定值sum
来源:互联网 发布:开一个网络主播工作室 编辑:程序博客网 时间:2024/05/01 01:53
这个问题是我从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。代码如下:
void CalSum(vector<int> &nums, int result) { int len = nums.size(); int bit = 1 << len; for (int i = 1; i < bit; i++)//从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) { for (vector<int>::iterator iter = tmp.begin(); iter != tmp.end(); iter++) { cout << *iter << " "; } cout << endl; } } }
网上有个评论说这个方法其实可以进行剪枝优化,原评论如下:
我们先对数字排个序{-10, 6, 9, 10, 17, 18, 20, 35, 45, 99}, 当二进制数为1001110000,已经算出35了那么1001110001-1001111111其实都是不用算的(肯定大于35),同样0001110000已经大于35了,可也需要不少次无用的循环校验,才能进位到0010000000,如果能把中间这些无用的循环略过,效率还能有很大提高!
根据这个评论提示,也就是如果1001110000已经为35了那么下一个就是看1010000000,如果1001010000是35那么下一个看1001100000。那么后面那个数字是怎么算出来的呢,我们可以发现这些数字的共同点就是最左边的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++; } }
说了这么多了,咱们赶紧进入正题,从长度为n的数组里选出m个数使和为固定值sum。
我们可以在第三个代码的基础上修改,每选出一个二进制数,我们可以先计算这个二进制数中1的个数(也可以在后面计算)如果个数等于m,再对这个m个数相加看是否等于sum。代码如下:
int 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; } } } }
参考:
http://www.tuicool.com/articles/QVR3auv
http://bbs.csdn.net/topics/391897373
http://blog.csdn.net/min_jie/article/details/3966867
http://bbs.csdn.net/topics/300102937
http://www.cnblogs.com/iyangyuan/p/3908498.html
0 0
- 每日一练——从长度为n的数组里选出m个数使和为固定值sum
- 从1-n这n个数里面,随机选出若干个数,使之和为sum
- 【转载】从长度为n的数组中选择m个数的所有结果
- 从一个长度为n的数组中随机选择m个数
- 从长度为n的数组中(元素互不相同)任意选择m个数的所有组合
- 从M个数中随机选出N个数的所有组合,无序,(一)
- 从大小为n的数组中随机选出m个整数,要求被选中的概率相同
- 在n个整数中选k个,使选出来的数的和为sum
- 每日一道算法题:一个整数数组,长度为n,将其分为m份,使各份的和相等,求m的最大值
- 从1到n的数中找若干个数使其和为m
- 输入n,m,从1-n个数字里输出和为m的组合
- 从数组中选择m个数,使得和为sum,递归写法
- 列出长度为n的数组中,所有加和为m的数
- 从长度为N的数组中找出所有M个元素组合的优化算法
- 从长度为M的无序数组中找出N个最大的数
- 从大小为n的数组中取出m个数的组合
- 从n个数中选择m个数,使其和为s
- 程序员面试金典——解题总结: 9.18高难度题 18.3编写一个方法,从大小为n的数组中随机选出m个整数。要求每个元素被选中的概率相同。
- 【杭电2006】奇数的乘积
- HMI
- reactJS - 02分离文件
- c语言第一天 终端的使用 .c .o .out 文件的解释 快捷键等
- 高效查看MySQL帮助文档的方法
- 每日一练——从长度为n的数组里选出m个数使和为固定值sum
- 算法-求n内的所有质数
- 判断图片是否加载完成的三种方式
- MySQL常见命令总结及资料汇总
- 获得C币规则
- 全国电子设计竞赛 (经验之谈)
- C - Ekka Dokka
- 逻辑数据库设计 - 单纯的树(递归关系数据)
- ASR自动语音识别技术