从N个数中取出任意个数,求和为指定值的解,二进制版本和通用版本
@
1、二进制版本
从N个数中取出任意个数,求和为指定值的解,并输出对应的数,思想就是二进制思想,从N个数选任意个2^N 种可能,数组里面的每一元素可能被选中,可能不被选中,1代表选中,0代表不选中。假如N=3,哪么就有2^3=8种可能。000---111。000代表一个数都不选,111代表三个数都选。这种方法的缺点是 :一旦数组元素过大,数据量成指数增长。只是用来算小数据量的。
c++代码如下
#include <iostream>
#include <string>
#include <algorithm>
using std::cout;
using std::cin;
using std::endl;
using std::string;
using std::string;
using std::to_string;
using std::count;
void CalSum(int array[], int len,int tagertSum)
{
long max_select=1; //max_select 代表有2^len中选择
for (int i = 0; i < len; i++) {
max_select *= 2;
}
for (int i = 1; i < max_select; i++) { //从1循环到2^len-1。从1开始, 因为一个都不选肯定无法达到要求, 2^N-1就代表全都选
int sum = 0;
string temp = to_string(tagertSum) + " = ";
for (int j = 0; j < len; j++){
if ((i & 1 << j) != 0){ //用i与2^j进行位与运算,若结果不为0,则表示第j位不为0,从数组中取出第j个数
sum += array[j];
temp += to_string(array[j]) + " + ";
}
}
if (sum == tagertSum) { //如果满足条件
temp = temp.substr(0, temp.length() - 2); //删除最后的一个空格和加号
cout << " 成功,i= " << i <<'\t' << temp << endl; //如果和为所求,则输出
}
}
}
int main()
{
int array[] = {10, 25, 35, 10, 6, 9, 20, 17, 8};
int len = sizeof(array) / sizeof(int);
CalSum(array, len,35);
return 0;
}
二进制版本的思想可类比到特征选择里面去 二进制思想与特征选择
2、通用版本
通用版本思路: 先排序。确定第一个数nums[i],问题就变成在数组num[i]后中查n-1个数的和为target1=target-num[i]的问题了。同理,在数组num[i]后确定第一个数nums[j]。问题也可以变成在数组num[j]后中查n-2个数的和为target2=target1-num[j]的问题。。不断向下分割。
啥时候结束查找呢,1是查找失败:发现某个子问题的targt(k)小于0或者当前位置往后没有这么多数了。2是查找成功:发现n个数的和等于target了,3是纯属遍历到最末尾了。
bool calNextNumber(int start, int target, int N, vector<int>&temp, vector<int>nums)
{
if (target == 0 && N == 0) //目标值减为0且个数N变成0说明找到了,返回ture
return true;
if (start + N > nums.size() || target < 0) //目标值减到小于0说明之后的值都不可以了(之后的值肯定比当前值要大,我们排过序),还有就是当前位置往后没有N个数了也不行,返回false
return false;
for (int i = start; i < nums.size(); ++i) { //这儿本来还可以加上一个判断,如果target比数组最大值大则循环到数组最后,但如果小,就循环到比target小的位置就行,但似乎没有改进太多性能,就没弄了
temp.push_back(nums[start]); //把开始位置的值加入本次序列里面
if (calNextNumber(i + 1, target - nums[start], N - 1, temp, nums)) { //从数组中找n-1个数和为target是否可行
return true;
}
temp.pop_back();//不可行就把当前这个开始位置的值给弹出,这儿和前面的push_back对应。然后从下一个位置继续找
}
return false;
}
vector<vector<int>> cal(vector<int> nums, int target, int N)
{
vector<vector<int>> result; //最终结果,相当于一个二维数组
vector<int> temp; //中间容器用于存放每次序列的数据,可以把它当做一个一维数组
for (int start = 0; start < nums.size()-N; ++start){
if (calNextNumber(start, target, N, temp, nums)) { //从数组中找n个数和为target是否可行,找到ivec里面是N个数,我这儿传的是引用
result.push_back(temp); //找到就加入总序列
}
temp.clear(); //然后不管找没找到都把这个temp中间容器给清空。方便下一次用
}
return result;
}
vector<vector<int>> n_NumberSum(vector<int>& nums, int target,int N) {
sort(nums.begin(), nums.end()); //先排个序,便于后面除去不必要操作
vector<vector<int>> result = cal(nums, target, N);
for(int i=0;i<result.size();++i){ //输出调试用
for(int j=0;j<3;++j){
cout<<result[i][j]<<" ";
}
cout<<endl;
}
return result;
}
int main()
{
int taregt=35,N=3; //这些自己指定
vector<int> nums;
....... //把nums赋值
vector<vector<int>> result=n_NumberSum(nums, target, N);
return 0;
}