从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;
}
posted on 2021-06-09 22:27  雾恋过往  阅读(614)  评论(0编辑  收藏  举报

Live2D